70-536: Using Value Types

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

Framework Microsoft .NET posiada dwie podstawowe grupy zmiennych – wartościowe, przekazywane przez wartość – np. Decimal, oraz referencyjne - przekazywane przez referencję, np. String. Postaram się przedstawić pierwszą grupę zmiennych.

Typy wbudowane

Typy przekazywane przez wartość stanowią dużą cześć wbudowanych typów frameworka .NET. Przykładem są Value Types (np. System.Byte, System.Int32, System.Double)  – wszystkie są przekazywane przez wartość. Oto przykład pokazujący różnicę zachowania typu wartościowego i referencyjnego:

   1: // przykład 1 - Value Type
   2: System.Int32 licznik = 7; // int
   3: System.Int32 licznik2 = licznik;
   4: licznik2 += 1;
   5:  
   6: Console.WriteLine(licznik + ", " + licznik2); // 7, 8
   7:  
   8: // przykład 2 - Reference Type
   9: ArrayList tablica = new ArrayList {1, 2, 3};
  10: ArrayList tablica2 = tablica;
  11: tablica2.Add(7);
  12:  
  13: Console.WriteLine(tablica.Count+", "+tablica2.Count); // 4, 4

W pierwszym wypadku zmiana jednej z wartości nie powoduje zmiany drugiej. Instancje zmiennych typów wartościowych bezpośrednio przechowują wartość na stosie. Stos jest strukturą, gdzie dane mogą być dodawane na wierzch stosu i z wierzchołka stosu pobierane. Jest to szybkie rozwiązanie, które gwarantuje dużą wydajność. Przypisanie powoduje skopiowanie danych, obie zmienne przechowywane są w innym miejscu na stosie (w przypadku przekazania zmiennej wartościowej jako parametru wywoływanej metody także operujemy na jej kopii!).

W drugim przypadku do zmiennej tablica2 nie zostały przekazane wartości, a referencja - wskaźnik, odnośnik do miejsca w pamięci, gdzie przechowywane są wartości. Dwie nazwy wskazują teraz na ten sam obszar pamięci.

Inne, często spotykane zmienne wartościowe, to np. System.Char, System.Boolean, System.IntPtr, System.DateTime.

Więszkość typów podstawowych ma zdefiniowane aliasy, które są równoważne wykorzystaniu pełnych nazw typów. Przykładowo zamiast System.Uint32 możemy stosować alias uint.

Warto zauważyć także, że tworząc zmienne nie wykorzystujemy słowa kluczowego new – konstruktor takich typów jest ukryty, deklaracja zmiennej tworzy instancję zmiennej, przypisując jej przy tym domyślną wartość.

Jeżeli chcemy mieć możliwość stwierdzenia, czy zmiennej została przypisana wartość, możemy określić zmienną jako Nullable. Pozwala to przypisać jej wartość null. Poniższy kod pozwala utworzyć zmienną typu Boolean, która może przyjmować wartości true, false lub null.

   1: Nullable<bool> b = null;
   2: Nullable<int> i = null;
   3: // lub krócej…
   4: bool? b = null;
   5: int? i = null;

Zmienną, która w ten sposób „awansuje”, możemy sprawdzić i stwierdzić, czy ma ona ustawioną wartość, czy nie. Wykorzystujemy w tym celu pola HasValue i Value.

image

Wyliczenia

Wyliczenie dostarcza listę wyborów, które są zbiorem określonych wartości powiązanych ze sobą. Zwiększają one czytelność i bezpieczeństwo kodu. Jako przykład możemy sobie wyobrazić pole typu numerycznego, które przechowuje liczbę reprezentującą poziom uprawnień użytkownika. Jest to rozwiązanie niebezpieczne, gdyż zbiór wartości jest ograniczony jedynie typem zmiennej. Zastosowanie wyliczenia pozwala wypisać zbiór poziomów, ograniczyć wartości pola tylko do nich. Inny przykład:

   1: enum Genders : int { Male, Female };

Definiujemy typ wyliczeniowy Genders (płcie), który może przyjąć dwie wartości: Male (mężczyzna) i Female (kobieta).

   1: class Program
   2: {
   3:     enum Genders { Male, Female}; // enum Genders : int { Male = 0, Female = 1}
   4:     static void Main(string[] args)
   5:     {
   6:         Genders gender = Genders.Male;
   7:         gender = (Genders)1; // Female
   8:         Console.WriteLine(gender.ToString()); // Female
   9:         Console.WriteLine((int)Genders.Male + ", " + (int)Genders.Female); // 0, 1
  10:         Console.ReadKey();
  11:     }
  12: }

Przy okazji ujawnia się ciekawe zachowanie:

   1: gender = 0; // Male, 
   2: gender = (Genders) 0; // Type cast is redundant
   3: //gender = 1; // błąd!
   4: gender = (Genders) 1; // działa, rzutowanie wymagane!

Inna ciekawosta:

   1: enum Genders { Male = 3, Female};
   2: gender = (Genders)1;
   3: Console.WriteLine(gender.ToString()); // 1

Typy zdefiniowane przez użytkownika

Zakładamy, że chcemy utworzyć byt, który ma składać się wyłącznie z typów wartościowych. Ma przy tym być tak wydajny, jak to tylko możliwe. Także przekazując go jako parametr metody, chcemy aby nie ulegał zmianie.

Co będzie najlepszym rozwiązaniem? Struktura – definiowana przez użytkownika, przechowywana na stosie, niemal identyczna do klasy. Grupuje ona powiązane dane, co sprawia, że łatwiej się na nich pracuje. Takim przykładem jest System.Drawing.Point.

   1: System.Drawing.Point p = new System.Drawing.Point(20, 30);
   2: Console.WriteLine("Point X {0}, Y {1}", p.X, p.Y);

Podczas inicjowania struktur korzystamy ze słowa kluczowego new.

Przykład struktury z wykorzystaniem poprzednio utworzonego typu wyliczeniowego Genders:

   1: struct Person
   2: {
   3:     public string firstName;
   4:     public string lastName;
   5:     public int age;
   6:     public Genders gender;
   7:  
   8:     public Person(string firstName, string lastName, int age, Genders gender)
   9:     {
  10:         this.firstName = firstName;
  11:         this.lastName = lastName;
  12:         this.age = age;
  13:         this.gender = gender;
  14:     }
  15:  
  16:     public override string ToString()
  17:     {
  18:         return firstName + " " + lastName + "("+ gender +"), age " + age;
  19:     }
  20: }

Musimy pamiętać, aby struktura spełniała pewne kryteria:

  • Logicznie reprezentuje pojedynczą wartość (np. punkt)
  • Rozmiar instancji nie zajmuje więcej niż 16 bajtów
  • Nie będzie zmieniana po utworzeniu (refleksja)
  • Nie będzie rzutowana na typ referencyjny

Jeśli chcemy zamienić strukturę w klasę wystarczy zmiana słowa kluczowego ze struct na class.

Kolejny artykuł z serii to 70-536:Using Common Reference Types

Tagi: , , ,

Comments

trackback
dotnetomaniak.pl
10/23/2009 9:54:22 AM Permalink

70-536: Using Value Types | Eastgroup.pl

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

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

Pingback from jarzynka.boo.pl

Certyfikat 70-563 | DanielJarzynka.net

Kola
Kola Poland
1/26/2010 7:37:04 PM Permalink

Hej,
A czy możesz wytłumaczyć jakim typem zmiennej jest string ?
Od początku wydawało mi się, że jest to typ referencyjny ale zrobiłem przygład podobny do twojego z ArrayList. Wyszło na to że string jest pół na pół. Co ty na to ?

kml
kml Poland
1/28/2010 11:01:08 AM Permalink

@Kola: Miałeś rację z tym, że string jest typem referencyjnym (i nie może być inaczej!). Twój kod, po pewnym rozwinięciu, mógłby wyglądać tak:

            // String - przykład 1
            string napis = "grupa.net"; // int
            string napis2 = napis;
            Console.WriteLine(Object.ReferenceEquals(napis, napis2)); // True - ten sam obiekt
            napis2 += " eastgroup.pl";
            Console.WriteLine(Object.ReferenceEquals(napis, napis2)); // False - inne obiekty
            Console.WriteLine(napis + ", " + napis2); // grupa.net, grupa.net eastgroup.pl

             // String - przykład 2
            ArrayList tablica = new ArrayList {"ala", "ma", "kota"};
            ArrayList tablica2 = tablica;
            tablica.Add(7);
            Console.WriteLine(tablica.Count+", "+tablica2.Count); // 4, 4

W pierwszym przykładzie zapewnie spodziewałeś się wyniku grupa.net eastgroup.pl, grupa.net eastgroup.pl zamiast grupa.net, grupa.net eastgroup.pl. String jest jednak niemutowalny (ang. immutable), co oznacza, że każda jego zmiana powoduje utworzenie nowego obiektu (stringa).

kml
kml Poland
1/28/2010 11:23:04 AM Permalink

Oto przykład innego ciekawego zachowania stringa:

  String napis = "grupa";
  String napis2 = "grupa";

  Console.WriteLine(Object.ReferenceEquals(napis, napis2)); // True! - ten sam obiekt
  Console.WriteLine(napis == napis2); // True (przeciążony operator "==" - porównywanie ciągów znaków)


Dlaczego mamy dwa razy ten sam obiekt? Daniel zasugerował,że wynika to zapewne z optymalizacji, którą przeprowadza kompilator.

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading