70-536 Constructing Classes

Poniższy artykuł pochodzi z serii Przygotowań do egzaminu 70-536.

Po tym artykule powinieneś choć trochę dowiedzieć się na temat dziedziczenia, interfejsów, typów generycznych. No ale od początku…

Dziedziczenie

Cały .NET Framework to tysiące klas posiadających przeróżne metody i właściwości. Nie było by możliwe korzystanie z jego dobrodziejstwa gdyby nie został zrobiony z pełną konsekwencją. Musimy mieć świadomość, że wszystkie typy danych ostatecznie dziedziczą z klasy podstawowej System.Object. Training Kit przytacza nam dla przykład metodę ToString() którą posiada każda klasa i która ma zawsze za zadanie zwrócić nam tekstową reprezentacje obiektu (<przestrzeń nazw>.<nazwa klasy>). Podobnie jest z metodą Equals() która porównuje nam elementy i zwraca TRUE wtedy i tylko wtedy kiedy są to te same instancje w pamięci. Zachowanie spójności frameworka jest możliwe dzięki zastosowaniu dziedziczenia oraz interfejsów.

Interfejsy

Interfejs to kontrakt, który gwarantuje klientowi, że klasa która obsługuje interfejs musi obsłużyć wszystkie jego składowe. Np. wbudowany interfejs IComparable definiuje metode CompareTo() która porównuje dwa obiekty w kolekcji co w konsekwencji pozwala nam posortować elementy danej kolekcji.

Powszechnie używane interfejsy:

Oczywiście IComparable

IDisposable - Definiuje metody usuwania obiektu ręcznie. Interfejs ten jest ważny dla dużych obiektów, które zużywają znaczne środki, lub obiektów, takie jak bazy danych, aby zablokować dostęp do zasobów.

ICloneable- obsługuje kopiowanie obiektu.

IFormattable- Umożliwia konwersję wartości obiektu w specjalnie sformatowany string. Zapewnia to większą elastyczność niż podstawowa metoda ToString.

IEnumerator- Umożliwia dostęp do wszystkich elementów kolekcji za pomocą instrukcji foreach.

I wiele innych ;)

What Are Partial Classes?

Skoro został poruszony temat na trzy linijki w Training Kit to i ja musze o nim wspomnieć. Czuje się jak ta klasa która deklaruje, że obsłuży wszystkie składowe interfejsu ;) Kiedy oznaczymy klasę słowem kluczowym partial to oznacza, że reszta definicji klasy znajduję się w odrębnym pliku. Jest to wykorzystywane np. w Windows Forms. Pozwala to ukryć szczegóły definicji klasy tak aby programista skupił się na bardziej znaczących częściach klasy.

Generics

Typy generyczne to część systemu CLR który pozwala na określenie typu pozostawiając pewne szczegóły nieokreślone. Nie musimy do końca określać rodzajów parametrów, możemy zezwolić na kod który używa klasy z ogólnie określonymi parametrami. .NET Framework zawiera różna klasy do korzystania z typów generycznych System.Collections.Generic zawiera m.in. Dictionary,Queue, SortedDictionary czy SortedList. Działają one podobnie jak ich niegeneryczne odpowiedniki w System.Collections ale oferują one lepszą wydajność i bezpieczeństwo typu. W C# 1.0 nieokreślony typ danych trzymano w typie object. Rzutowano dane na typ object a następnie kiedy chcieliśmy z nich skorzystać musieliśmy kolejny raz rzutować na potrzebny nam typ. Ten tkz. Boxing i Unboxing (o którym szerzej będzie dalej) zużywa niepotrzebnie moc procesora i spowalnia działanie. Mamy tu już pierwszy plus korzystania z typów generycznych – wydajność.
Kolejnym plusem jest to, że kompilator potrafi sam wykryć błędy i nie doprowadzi do uruchomienia programu kiedy ich nie poprawimy. Bo wyobraźmy sobie sytuacje, że rzutujemy string na object, następnie ten object na int, kompilator nie dostrzeże tutaj błędu co jak nie trudno się domyśleć potem może nas to kosztować wyjątkiem. Używając typów generycznych kompilator dostrzega niezgodność typów, również sami możemy sobie nałożyć ograniczenia na klase dzięki czemu kompilator wcześniej nas ostrzeże przed błędem.

Trochę to zagmatwane ale spójrzmy na klasy które będą realizowały to samo zadanie ale klasa Obj będzie użwała do tego celu typu object a klasa Gen typów generyczny. (Przykłady zaczerpnięte oczywiście z training kit)

   1: class Obj
   2:     {
   3:         public Object t;
   4:         public Object u;
   5:         public Obj(Object _t, Object _u)
   6:         {
   7:             t = _t;
   8:             u = _u;
   9:         }
  10:     }
  11: class Gen<T, U>
  12:     {
  13:         public T t;
  14:         public U u;
  15:         public Gen(T _t, U _u)
  16:         {
  17:             t = _t;
  18:             u = _u;
  19:         }
  20:     }

 

Jak widzimy klasa Obj posiada dwa pola typu object natomiast klasa Gen dwa pola typu T i U. Przy korzystaniu z takiej klasy to od nas zależy na jaki typ będą wskazywały T i U…może to być jakiś string, int, niestandardowa klasa bądź jakaś inna kombinacja typów.

Jak teraz z tego skorzystać?

Dodajmy po dwa napisy do naszych klas

   1: Obj oa = new Obj("Hello, ", "World!");
   2:             Console.WriteLine((string)oa.t + (string)oa.u);
   3:  
   4: Gen<string, string> ga = new Gen<string, string>("Hello, ", "World!");
   5:             Console.WriteLine(ga.t + ga.u);

 

oraz po dwie liczby typu double oraz wyświetlmy ich sumę

   1: Obj ob = new Obj(10.125, 2005);
   2: Console.WriteLine((double)ob.t + (int)ob.u);
   3:        
   4: Gen<double, int> gb = new Gen<double, int>(10.125, 2005);
   5: Console.WriteLine(gb.t + gb.u);

 

Kiedy uruchomimy program zauważymy, że w obu przypadkach wszystko ładnie chodzi. Trzeba jednak mieć świadomość, że klasa Gen wykonuje się szybciej gdyż nie wykonuje tego opakowywania w typ object (boxing) a następnie rozpakowywania (unboxing). Poza tym spojrzmy na korzyść płynącą z tego, że nie musimy właśnie bawić się w to rzutowaniem. A teraz zobaczymy jak to jest z tym sprawdzaniem błędów typu na etapi kompilacji, wprowadźmy sobie kod:

   1: Gen<double, int> gc = new Gen<double, int>(10.125, 2005.45);
   2: Console.WriteLine(gc.t + gc.u);
   3:  
   4: Obj oc = new Obj(10.125, 2005);
   5: Console.WriteLine((int)oc.t + (int)oc.u);

 

A jednak nie kłamali ;) Kompilator wstrzyma kompilacje i wyrzuci nam błąd a nawet dwa ;) Ale żeby nie było złudzeń tylko do klasy Gen. Jeśli chodzi o klase Obj to kompilator nie widzi nic złego ale my po chwili działania programu dostrzeżemy piękny wyjątek ;)

Jak korzystać z ograniczeń

Użycie klauzuli where w C# nakłada pewne ograniczenia. Na poniższym przykładzie zademonstrowano klase z której mogą korzystać tylko typy które implementują interfejs IComparable

   1: class CompGen<T>
   2: where T : IComparable
   3: {
   4: public T t1;
   5: public T t2;
   6: public CompGen(T _t1, T _t2)
   7: {
   8:    t1 = _t1;
   9:    t2 = _t2;
  10: }
  11:  
  12: public T Max()
  13:     {
  14:         if (t2.CompareTo(t1) < 0)
  15:            return t1;
  16:        else
  17:           return t2;
  18:     }
  19: }

Klasa skompiluje się i wszystko będzie ok. Jednak kiedy usuniemy klauzule where kompilator zgłosi błąd bo klasa nie zawiera definicji CompareTo().

Kolejny artykuł z serii to 70-536: Converting Between Types

Tagi: , ,

Comments

trackback
dotnetomaniak.pl
10/28/2009 10:14:27 AM Permalink

70-536 Constructing Classes

Dziękujemy za publikację - Trackback z dotnetomaniak.pl

pingback
jarzynka.boo.pl
1/11/2010 12:15:44 PM Permalink

Pingback from jarzynka.boo.pl

Certyfikat 70-563 | DanielJarzynka.net

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading