Słowem wstępu..
W języku C# mamy 4 kategorie typów:
– typy wartościowe
– typy referencyjne
– parametry typów generycznych
– typy wskaźnikowe
Jak pewnie się domyślasz, w tym wpisie poznasz różnicę pomiędzy dwoma pierwszymi.
Dlaczego warto znać te różnicę? Przede wszystkim jest to częste pytanie rekrutacyjne, na które warto się przygotować. Nie jest to nic skomplikowanego i tym bardziej warto kojarzyć te pojęcia. Nie jest to też wiedza, którą będziesz posługiwał się każdego dnia. Powiedziałbym, że jest to taki detal, który warto mieć “z tyłu głowy”. 🙂
Zaczynajmy!
Co musisz zapamiętać?
Do typów wartościowych zalicza się większość typów wbudowanych czyli:
– wszystkie liczbowe (int, decimal, double itd.)
– char i bool
– oraz struct i enum
Do typów referencyjnych zalicza się:
– wszystkie klasy,
– tablice
– delegacje oraz interfejsy
– string
Co jest fundamentalną różnicą między tymi kategoriami typów? Czyli czym się różnią typy wartościowe od referencyjnych?
Różnica dotyczy sposobu przechowywania ich wartości w pamięci. Zawartością zmiennej lub stałej typu wartościowego jest po prostu jedna wartość, np. dla typu int są 32 bity danych. Natomiast typy referencyjne składają się z dwóch części: obiektu i referencji – odniesienia do obiektu zawierającego wartość.
W pamięci typy wartościowe trafiają w sposób uporządkowany na stos (z ang. Stack), natomiast typy referencyjne trafiają na stertę (z ang. Heap), czyli tam gdzie jest to możliwe.
Bardzo ważne jest, aby wiedzieć, jaka jest jeszcze inna ciekawa różnica pomiędzy tymi typami. Istnieje też wyjątek od tej reguły, o którym wspomnę niżej.
Przypisywanie wartości niesie za sobą różne konsekwencje.
Przykład – dla typów wartościowych
Przypisanie wartości powoduje tylko jego skopiowanie
1 2 3 4 5 6 7 8 9 10 |
//Deklarujemy typ wartościowy int i przypisujemy wartość 20 int a = 20; //Kopiujemy wartość do zmiennej b - ponieważ jest to typ wartościowy. int b = a; //Zmieniamy wartość pierwszej zmiennej a a = 40; //Co otrzymujemy? Console.WriteLine(b); //Otrzymamy: 20 |
Jak zauważyłeś nie została zmodyfikowana zmienna “b”, czyli przypisanie “int b = a”, i aktualizacja zmiennej “a”, nie spowodowała zmiany wartości zmiennej “b”.
Teraz jak to się ma dla typów referencyjnych?
Stwórzmy sobie klasę, która będzie naszym typem referencyjnym.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
using System; namespace Test_Wiedza { public class Person { public Person() { } public string Name { get; set; } = "Ala"; } class Program { static void Main(string[] args) { //p1 o imieniu Ala Person p1 = new Person(); //przypisujemy p1 do p2 Person p2 = p1; //zmieniamy imię pierwszego obiektu p1.Name = "Stefania"; //wyświetlamy imię drugiego obiektu Console.WriteLine(p2.Name); //Otrzymujemy: Stefania Console.ReadKey(); } } } |
Jak pokazał powyższy przykład, przypisanie obiektu “p1” do obiektu “p2” spowodowało, że utworzyła się referencja – czyli wskazanie na konkretną wartość w pamięci. Dlatego też każda modyfikacja obiektu “p1” będzie widoczna w obiekcie “p2”
Proste i logiczne. Natomiast, jest pewien wyjątek. Typ string jest typem referencyjnym, natomiast przypisanie działa jakby był typem wartościowym.
Zobacz przykład.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
using System; namespace Test_Wiedza { class Program { static void Main(string[] args) { string text = "Ala ma kota"; string textCopy = text; text = "Kot ma AIDS"; Console.WriteLine(textCopy); //Otrzymamy: Ala ma kota Console.ReadKey(); } } } |
Dlaczego tak się dzieje? Szczerze mówiąc, szukałem odpowiedzi na ten temat w internecie i większość odpowiedzi wskazywała na to, że typ string może mieć bardzo dużą wartość, i po prostu musi być przechowywany na stercie (z ang. Heap). Dlatego, też jest typem referencyjnym, a nie wartościowym.
Czy zawsze typ wartościowy trafi na stos?
Takie pytanie zadał mi mój kolega, który przeczytał sobie ten wpis. Jest to bardzo ciekawe pytanie, ponieważ odpowiedź brzmi NIE, np. gdy mamy klasę, która posiada w sobie własność np. typu int, taka własność trafi wówczas na stertę. 🙂