Value and RefCzęść NR.3Typy takie jak int, float ,double ,char są typami wartościowymi. Kiedy deklarujesz zmienną jako typ wartościowy, kompilator alokuje kod w wystarczająco dużym bloku pamięci. 

Czyli dla typu int kompilator rezerwuje 32 bitów pamięci, ponieważ typ ten składa się z 4 bajtów.

 

 

int a = 121;

Wyrażenie, które przypisuje wartości powoduje kopiowanie wartości do bloku pamięci.

Sprawa wygląda zupełnie inaczej z klasami. Kiedy deklarujesz obiekt klasy kompilator nie generuje kodu, który zarezerwowałby odpowiedni obszar pamięci dla danego obiektu. Kompilator w pamięci przechowuje adres (referencje) do innego bloku pamięci, gdzie jest przechowywana klasa. Pamięć ta dla klasy jest lokalizowana wtedy, gdy tworzymy nowy obiekt za pomocą słowa kluczowego new.

Klasa jest typem referencyjnym. Referencje trzymają adres konkretnego bloku pamięci.

Zrozumienie typów referencyjnych i wartościowych jest bardzo ważne. Nie chcemy aby w przyszłości nasze programy popełniały głupie błędy.

A co ze zmienną string
String też jest typem referencyjnym . Czyli opis zachowania klas w pamięci będzie podobny do łańcucha znaków. Większość wbudowanych typów w C# jest typem wartościowym. Jednak słowo kluczowe string de facto jest aliasem do klasy System.String.

Kopiowanie wartości w klasach i w typach wartościowych wygląda zupełnie inaczej. Jest to jeden z podstawowych błędów, jakie można popełnić w programowaniu.

Kopiowanie wartości


W sytuacji, w której chcemy stworzyć kopie jakiejkolwiek zmiennej, która jest typem wartościowym nie pojawiają się żadne problemy. Wszystko działa tak jak należy. Operacje wykonane na kopii, czy na oryginale są niezależne od siebie, ponieważ zmienne te znajdują się w dwóch różnych blokach pamięci.

klass typy referencyjne-02

Oto przykład kodu.Operacje na typach wartościowych
Sprawa wygląda zupełnie inaczej przy klasach, czyli typach referencyjnych.

Cuboid cu = new Cuboid(2,3,6);
Cuboid cu2ref = cu;

Zmienna "cu" tak naprawdę przechowuje tylko referencje do obiektu. Przyrównując ją do innej zmiennej cu2ref tak naprawdę nie tworzymy kopii obiektu, tylko dodajemy kolejną referencje do tego samego obiektu. Jakakolwiek zmiana w cu czy cu2ref będzie zależna od siebie, ponieważ mówimy tu ciągle o tym samym obiekcie.

To prawda , że kompilator alokuje dwa obszary pamięci , ale te obszary przechowują tą samą referencje. Oto ilustracja obrazująca tę sytuację. Znaczek @ symbolizuje przechowywaną referencję.
klass typy referencyjne-01
Trzy kółka symbolizują całe ciało obiektu . Rysunek może być trochę mylący. Klasa Cuboid zawiera trzy pola prywatne.

Jak już mówimy o referencjach trzeba byłoby coś powiedzieć o tym ,że istnieje możliwość przechowywania pustych referencji oraz pustych wartości. O tym w następnej części kursu.

Organizacja pamięci

Komputery używają pamięci do zapisywania stanu obiektów znajdujących się w aplikacji. Aby zrozumieć różnicę pomiędzy typem wartościowym  a typem referencyjnym dobrze jest wiedzieć jak dane są zorganizowane w pamięci.

Języki programowania w tym C# zazwyczaj dzielą pamięć na dwa różne zbiory i każdy z nich jest zarządzany w inny sposób. Te dwa zbiory mają oczywiście swoje fachowe nazwy. Stos i sterta. Oba spełniają bardzo różne od siebie cele.

Kiedy wywołujesz metodę . Pamięć wymagana dla parametrów i lokalnych zmiennych jest zawsze rzucona na stos. Kiedy metoda skończy swoje działanie, pamięć zabrana przez parametry i zmienne lokalne zostaje zwrócona i wyciągnięta ze stosu. Metoda skończy swoje działanie, gdy pojawił się wyjątek lub gdy zwróci podaną wartość, bądź dojdzie do końca. Fragment stosu może być ponownie użyty przez inną metodę.

Kiedy tworzysz instancje klasy używając słowa kluczowego new to pamięć przeznaczona na zbudowanie obiektu idzie do sterty. Ten sam obiekt może mieć wiele referencji na starcie. Gdy ostatnia referencja do obiektu zniknie obszar pamięci jest czyszczony i może być użyty ponownie. Nie dzieje się to natychmiastowo i dużą rolę w stercie pełni automatyczny sprzątacz, który nazywa się Garbage Collector.

Wszystkie typy wartościowe są na stosie ,wszystkie typy referencyjne są tworzone na stercie.

Stos jest zorganizowany niczym jak pudełka, które są ułożone pionowo jedne pod drugim. Kiedy metoda jest wywoływana każdy parametr jest wkładany do pudełka i układany pionowo jedno pod drugim. Każda zmienna jest powiązana z zawartością tego pudełka i one są umieszczane pod pudełkiem na stosie. Po skończonej metodzie, tak jak pisałem wcześniej wszystkie pudełka zostają usunięte.

Sterta wygląda jak kilka poziomo ułożonych pudełek ustawionych oddzielnie od siebie. Każde z tych pudełek ma na sobie napis czy jest wciąż używane. Gdy tworzymy nową instancje obiektu program wyszukuje puste nie używane pudełko i umieszcza w nim obiekt. Kiedy ostatnia referencja znika w którymś momencie Garbage Collector opróżni pudełko i ustawi je do ponownego użycia.

Wiedząc to wszystko możemy już ustalić co stanie się gdy wywołałam następującą metodę.

static void UtworzeObiekt(int h,int b)
{
    int a = 6;
    Cuboid cu;
    cu = new Cuboid(a, b, h);
}

Parametry podane do metody powinny lądować na stos i na stertę w ten sposób. Chyba że zmienimy kolejność podawania parametrów za pomocą ich nazw.

klass typy referencyjne-03

Po skończeniu metody stos i sterta zostanie wyczyszczona. Teraz już wiesz, dlaczego nie możesz się odwołać do zmiennych, które istnieją tylko wewnątrz bloków kodu.

Jeśli chodzi o wyjątki. W wypadku przepełnienia stosu otrzymamy wyjątek “StackOverflowException” , a w wypadku przepełnia sterty “OutOfMemoryException”. Sterta ani stos nie mają nieskończonej ilości miejsca i zazwyczaj te wyjątki są sygnałem , że program się zapętlił np. utworzył kilkanaście tysięcy obiektów na stercie.

Podsumowanie

Aby głębiej zrozumieć stertę i stos trzeba powiedzieć coś o pakowaniu i wypakowaniu zmiennych. Proces, który zachodzi, gdy zmienne z obiektu przechodzą na swoje typy. A w C# wszystko jest obiektem i dziedziczy po klasie System.Object.