Blog programisty o C/C++ i PHP

Programowanie w C/C++ i PHP. Blog pełen wskazówek, porad, analiz i opisów.

Specjalizacja metod generycznych w C# ?

W języku C# podobnie jak w C++ można stworzyć klasy i/lub metody szablonowe. Jednakże te własności języka nie są tak bardzo rozwinięte jak w dużo starszym jego pierwowzorze. Niekiedy zdarza się potrzeba, aby implementacja metody szablonowej dla pewnych wyróżnionych typów zachowywała się inaczej niż standardowo. Służą do tego specjalizacje szablonów. W C++ mamy możliwość dokonania zarówno częściowej jak i pełnej specjalizacji wzorca metody. C# niestety nie posiada takiego narzędzia językowego i trzeba niestety sobie poradzić w inny sposób. Jeśli chcesz dowiedzieć się jak można to zrobić czytaj dalej.

Załóżmy, że mamy pewną klasę, która ma zadeklarowaną metodę read() służącą do odczytywania ze strumienia wartości. Jednakże w strumieniu mogą być zarówno dane binarne (liczby typu short, int czy long) jak również łańcuchy i znaki tekstowe.Chcielibyśmy mieć możliwość odczytywania wartości konkretnych typów, a  nie typu object. Można to zrobić tworząc oddzielne metody dla każdego typu np.: short readShort(), int readInt(), long readLong(), string readString(). Można także także stworzyć metodę generyczną T read<T>(). W tym przypadku pojawia się jednak problem ponieważ język C#  nie dysponuje jako taką możliwością specjalizacji wzorca metody, a przecież w naszym strumieniu sposób odczytu dla różnych typów może być odmienny. Przyjmijmy, że łańcuchy będą tymi wyjątkami i zapisane będą tekstowo w kolejności bajtów takiej jak występujące w łańcuchu znaki. Liczby z kolei będą zapisywane binarnie w kolejności od bajtu najstarszego do najmłodszego. Mniejsza o szczegóły. Ważne, że dla łańcuchów działanie jest odmienne niż dla pozostałych typów.

Ponieważ metody read() nie da się przeciążyć zmieniając tylko typ wyniku, ani także nie da się wyspecjalizować szablonowej metody read(), należy posłużyć pewnym dość interesującym rozwiązaniem. Wykorzystamy do tego celu fakt, że każdą metodę zadeklarowanego w dziedziczeniu interfejsu należy zaimplementować, oraz że interfejsy także mogą być szablonowe. Stwórzmy sobie szablonowy interfejs odczytujący o następującej postaci:

    public interface IReader<T>
    {
        T read();
    }

Następnie stwórzmy właściwą klasę odczytującą i nakażmy jej by realizowała interfejs czytający dla różnych typów. Dodajmy także publiczną uogólnioną metodę do odczytywania:

    public class MyReader
        : IReader<short>
        , IReader<int>
        , IReader<long>
        , IReader<string>
    {
        public MyReader(string sFileName)
        {
        }

        public T read<T>()
        {
            ((IReader<T>)this).read();
        }

        short IReader<short>.read()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        int IReader<int>.read()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        long IReader<long>.read()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        string IReader<string>.read()
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }

Jak widać uogólniona (szablonowa) publiczna metoda do odczytu próbuje zrzutować obiekt na którym została wywołana (this) do odpowiedniego interfejsu i wywołać na tak zrzutowanym obiekcie właściwą implementację metody czytającej. Metody implementacyjne poszczególnych interfejsów nie mogą być publiczne (kompilator nie dopuści), ponieważ mają taką samą sygnaturę argumentów (czyli brak argumentów) i nie dałoby się ich na zewnątrz rozróżnić, a przecież przeciążanie metod wymaga różnych ich sygnatur. Niestety mankamentem rozwiązania jest to, że rzutowanie następuje w runtime’ie, oraz że trzeba napisać wszystkie implementacje.

A na koniec przykład użycia:

            MyReader mr = new MyReader("myFile.bin");
            short si = mr.read<short>();
            int i = mr.read<int>();
            long l = mr.read<long>();
            string s = mr.read<string>();

Leave a Reply