BoxingCzęść NR.6 Wszystko jest obiektem i wszystko w C# dziedziczy po System.Object ,tylko co z tego.
Informacje zawarte w tym wpisie będą przydatne również przy dziedziczeniu klas.
Nie mam ochoty pisać wstępu więc od razu przejdźmy do rzeczy.
System.Object
Wszystkie klasy i struktury dziedziczą po typie object. Możesz też użyć typu object do stworzenia zmiennej, która może referować do każdego typu.
System.Object obj2;
object obj;
W kodzie System.Object i object znaczą dokładnie to samo.
Object może przechowywać referencje do tego samego obiektu na stercie. Ponieważ wszystkie klasy nawet te utworzone przez nas są również typem object ,taka operacja jest prawidłowa.
Ale co z typami wartościowymi.
Boxing pakowanie
Jak właśnie widać zmienne object mogą referować się do jakiekolwiek obiektu referencyjnego. Mogą też przechowywać typy wartościowe.
int a = 121;
object ob = a;
Jednak to proste wyrażenie wymaga wyjaśnienia. Zmienne typu wartościowego żyją na stosie , a zmienna object żyje na stercie, czyli ona jest typu referencyjnego . W wyniku tej operacji wartość ze stosu zmiennej “a” zostanie skopiowana na stertę i referencyjnie powiązana z zmienną typu object. Ta operacja nazywa się boxing – po polsku pakowaniem , a nie boksowaniem .
Ponieważ zmienna “ob” jest kopią zmiennej i w żaden sposób nie jest powiązana z oryginałem to jakakolwiek operacja nie zmieni wartości “a”. Tak samo żadna operacja na zmiennej “a” nie zmieni wartości na zmiennej “ob”.
UnBoxing wypakowywanie
O ile pakowanie obiektów do formatu object jest proste, to operacja działająca w drugą stronę już nie. Próba bezpośredniego przypisania wartości object do zmiennej typu int skończy się błędem kompilatora.
Jest to zrozumiałe, ponieważ zmienna object może przechowywać nie tylko zmienną int. Równie dobrze mogłaby przechowywać inny typ liczbowy albo referencje do obiektu klasy.
Aby zaradzić takiej sytuacji mamy do dyspozycji “rzutowanie” (cast). Jest to operacja, która sprawdza czy dany typ może być przekonwertowany na inny. Rzutowanie wykonuje się poprzez dopisanie nawiasów , a w nich typu, na który nasza zmienna mam być przekonwertowana.
int a = 121;
object ob = a;
int b = (int)ob;
Rzutowanie nie jest doskonałe. Kompilator po prostu teraz zauważy , że ma przekonwertować obiekt na int. Jeśli obiekt rzeczywiście przechowuje zmienną typu int rzutowanie wykona się poprawnie. Ta operacja nazwa się unboxing – czyli rozpakowywanie.
Jednak wciąż kompilator nie może sprawdzić, co ta zmienna “ob” będzie przechowywać podczas działania programu? Operacja ta wciąż nie jest bezpieczna . Zmienna ta może przechowywać dosłownie wszystko.
Jeśli zmienna object rzeczywiście nie przechowuje zmiennej, która może być rzutowana na int zostanie wyrzucony wyjątek “InvalidCastException”.
Powiem taką ciekawostkę , że rzutowanie z wartością liczbową na inną wartość liczbową działa bez problemu, dopóki konwertowana wartość może być zawarta w danym typie liczbowym. Ułamki przy rzutowaniu na liczby całkowite będą tracić swoje wartości dziesiętne. Czyli taka operacja jest legalna , ale bezpośrednie rzutowanie z obiektu musi być całkowicie zgodne z typem. Jednak C# nie jest , aż tak magiczny.
Rzutowanie też nie działa magicznie. Konwersja niektórych typów na inne musi być wykonana poprzez różne metody. Jak np. int może być przekonwertowany na łańcuch znaków tylko za pomocą metody “ToString()”. Wchodzimy tutaj powoli w tematykę polimorfizmu w klasach.
Rzutowanie obiektów w ten sposób jest opłacalne, tylko wtedy, gdy mamy pewność , że dana wartość może być zawsze konwertowana. Najczęściej jednak tak nie jest, dlatego w C# do dyspozycji mamy słowa kluczowe is i as.
Operator "is" czyli “czy jest”
Operator is potrafi zweryfikować, czy dany obiekt jest danego typu bądź jest potomkiem tej klasy (polimorfizm)
object o = new Cuboid(2, 4, 5);
int objetosc;
if (o is Cuboid)
{
Cuboid temp = (Cuboid)o;
objetosc = temp.Volume();
}
Operator do swojego działania potrzebuje dwóch operandów: obiekt , który chcemy sprawdzić i nazwę typu.
Jeśli dany obiekt jest danym typem bądź dziedziczy po nim wyrażenie, to zwróci wartość logiczną true.
W ten sposób kod w bloku if wykona się tylko wtedy, gdy rzeczywiście zmienna "o" przechowuje typ klasy Cuboid.
Operator "as" czyli jako
Moim zdaniem bardziej użytecznym operatorem jest jednak operator “as”. Zwłaszcza że zobaczysz go jeszcze nieraz.
Do działania potrzebuje tych samych operandów co operator is ,ale jego działania jest inne. Słowo kluczowe “as” sprawdza najpierw, czy operacja rzutowania może się wykonać. Jeśli operacja może zostać wykonana następuje rzutowanie. W przeciwnym wypadku operator zwróci referencje bądź wartość null.
Możesz tutaj zauważyć użyteczność typów wartościowych z rozszerzeniem nullable, ponieważ mogą one przechowywać wartości null.
object o = new Cuboid(2, 4, 5);
int? aa = o as int?;
Cuboid c = o as Cuboid;
if ((aa == null) && (c != null))
Console.WriteLine(c.Volume());
Poniższy kod napisze na konsoli objętość prostopadłościanu, ponieważ zmienna “c” rzutowała się prawidłowo , a zmienna “aa” przechowuje wartość null, bo rzutowanie się nie powiodło
Tyle o rzutowaniu
Nie bardzo. Rzutowanie i konwersja typów na inne zaczyna być fajną zabawą przy polimorfizmie.