(Nie)jawnaCzęść NR.17

Czasami w C# wstępuje konwersja z jednego typu do drugiego.

Powiedzmy ,że stworzyłem metodę X, która przyjmuje jeden parametr double.

 

 

 

class KlasaX 
{
    public static void MetodaX(double parametr)
    {
        Console.WriteLine(parametr);
    }
}

Z logicznego punktu widzenia metoda ta powinna przyjmować tylko wartości typu double. Jednak tak się nie dzieje.

Kompilator C# pozwala na wywołanie tej metody X z innym typem parametru pod warunkiem ,że ta wartość może być przez konwertowana na typ double.

Jeśli umieszczę parametr typu intw tej metodzie kompilator stworzy za mnie kod konwertujący tą wartośćint nadouble podczas wywoływania metody.

Konwersja jawna i niejawna

Wbudowane typy mają swoje wbudowane konwersje. Konwersja zaprezentowana poniżej nazywa się konwersją “rozszerzającą”, Ponieważ typ double zawiera więcej wartości niżint więc nie ma tutaj żadnej straty informacji.

Z drugiej strony typ double nie może być skonwertowany niejawnie w int.

class KlasaX 
{
    public static void MetodaY(int parametr)
    {};

Nie jawna konwersja z double na int

Taka konwersja jest konwersją stratną dlatego nie może być ona uznana i przeprowadzona automatycznie, czyli niejawnie. Pojawia się też problem z zaokrąglaniem, czy powinno być ono wykonane w górę, czy w dół np. dla wartości (0.5). W takim wypadku konwersja musi wykonać się jawnie poprzez rzutowanie.


KlasaX.MetodaY((int)3.14);

Tak konwersja nazywana jest konwersją zwężającą, ponieważ rezultat jest krótszy od swojej oryginalnej wartości i może wyrzucać wyjątek ”OverflowException”.

C# pozwala na określenie konwersji dla twoich własnych typów i możesz w nich określić, czy dana konwersja jest jawna, czy niejawna. Wracamy też do operatorów.

Stworzona przez ciebie konwersja jawna i niejawna

Styl pisana konwersji jest zbliżony do deklaracji przeciążonych operatorów z poprzedniego wpisu.

Operator konwersji musi być publiczny i statyczny. Oto kod, który będzie pozwalał na konwersje obiektu Minuty na int. Pojawia się tutaj też nowe słowo kluczowe “implicit”.

struct Minuty 
{
    public static implicit operator int (Minuty z)
    {
        return z.value;
    }
    //reszta kodu  

    //pole prywatne 
    private int value;

Jak widzisz w metodzie wartość zwrotna jest typu, który chcemy uzyskać w tym wypadku jest to int.Musi być on zadeklarowany po słowie kluczowym “operator”.

A potrzebnym parametrem w tej metodzie są “Minuty”, bo z tego typu chcemy wykonać konwersje.

Słowo kluczowe “implicit” informuje kompilator o typie konwersji .W tym wypadku zadeklarowaliśmy konwersje niejawną. Dla przypomnienia konwersja ta nie wymaga rzutowania i działa automatycznie.

Minuty doDzwonka = new Minuty(9);
KlasaX.MetodaY(doDzwonka); //nie jawna konwersja z Minuty na int

W analogiczny sposób możemy zadeklarować konwersje jawną za pomocą słowa kluczowego “explicit”.

struct Minuty 
{

    public static explicit operator int (Minuty z)
    {
        return z.value;
    }

    //reszta kodu 

Tylko w takim wypadku trzeba byłoby wykonać rzutowanie.

Minuty doDzwonka = new Minuty(9);

KlasaX.MetodaY((int)doDzwonka); // jawna konwersja z Minuty na int

Kiedy deklarować konwersje jawną a kiedy niejawną.

Jeśli konwersja jest zawsze bezpieczna, czyli nigdy nie wyrzuci wyjątku oraz nie jest konwersją stratną powinna być niejawna czyli automatyczna.

W tym wypadku konwersja ze struktury Minuty na int jest zawsze bezpieczna, ponieważ struktura ta zawiera w sobie tylko wartość int.

Zakładając ,że np. Minuty mogłyby przyjmować tylko wartości do 60 i nie mogłyby być ujemne to konwersja z int na Minuty musiała być jawna.

Jednym słowem konwersja jawna, to taka, w której programista zakłada , że dany użytkownik twojej klasy/struktury będzie wiedział jakie ryzyko wiąże się z daną konwersją i wie co robi.

Powrót do operatorów

W poprzednim wpisie opisałem , że w przypadku operacji symetrycznych trzeba pisać kilka wersji metody dla operatora +. Obecnie struktura Minuty ma trzy wersje operatora + jest to (Minuty+Minuty, Minuty +int, int + Minuty).

Wraz z pojawieniem się konwersji ten problem może być rozwiązany w inny sposób. W końcu, jeśli Minuty mogą być przekonwertowane na int to operacja (Minuty + int, int +Minuty) nie będzie już potrzebne.

Teraz struktura Minuty wygląda tak:

struct Minuty 
{
    //Metoda wykonująca konwersje niejawną z int na Minuty  
    public static implicit operator Minuty (int z)
    {
        return new Minuty(z);
    }

    //nadpisany operator plus  
    public static Minuty operator +(Minuty lhs, Minuty rhs)
    {
        return new Minuty(lhs.value + rhs.value);
    }

    //pole prywatne  
    private int value;

    //konstruktor  
    public Minuty(int firstValue)
    {
        value = firstValue;
    }

    //właściwość ,aby wartość była dostępna  
    public int Value
    {
        get { return this.value; }
        set { this.value = value; }
    }
}

Teraz gdy dodasz wartość Minuty z wartością int w jakiejkolwiek kolejności kompilator automatycznie skonwertuje wartość int na Minuty i przy dodawaniu wykona się metoda + z dwoma parametrami "Minuty".

Minuty m7 = new Minuty(30);

Minuty m8 = 20 + m7; // 20 skonwertowane na Minuty
Minuty m9 = m7 + 30; //30 skonwertowane na Minuty 

Co dalej:

Jedno słowo: Generics, ale najpierw delegaty.