Przyjrzyjmy się różnicom między value types a reference types.
Najpierw spróbujmy je sobie wyobraźić w bardziej życiowej sytuacji, bez programistycznego bełkotu.
Value type to "informacja sama w sobie". Taką daną mogłaby być na przykład karteczka z zapisanym czyimś numerem telefonu. Jeśli znajomy poprosi nam o przekazanie mu tego numeru, weźmiemy inną karteczkę i zapiszemy na niej ten sam numer. Jeśli z nudów zaczniemy bazgrać na naszej karteczce i zmienimy jej treść, karteczka naszego znajomego pozostanie bez zmian.
Reference type to ciężki wolumen w bibliotece, zawierający dużo danych. Możemy poprosić bibliotekarza o informację, gdzie możemy te dane znaleźć. Da nam on wtedy karteczkę z tytułem dzieła i numerem rzędu, gdzie się ono znajduje. Jeśli znajomy poprosi nas o pomoc i okaże się, że potrzebne mu są informacje z tej samej książki, łatwiej będzie skopiować treść naszej karteczki i wręczyć ją znajomemu, niż kopiować tysiącstronicowy wolumen. Z drugiej strony, jeśli my postanowimy napisać coś głupiego na marginesie jednej ze stron, nasz znajomy zobaczy to, kiedy będzie przeglądał książkę.
Tak właśnie działają value types i reference types.
- value type jest daną samą w sobie, reference type jest odnośnikiem do danych - adresem pamięci, gdzie możemy je znaleźć
- przypisanie zmiennej value type do innej zmiennej tworzy kopię danych, zaś w przypadku reference type kopiujemy tylko odnośnik do pamięci - obiekt w niej zapisany pozostaje taki sam - teraz odnoszą się do niego dwie referencje, nie jedna
- value types przechowywane są na stosie, zaś reference types na stercie. W tym poście nie będę się o tym rozpisywać - więcej informacji można znaleźć tutaj [under construction]
- Garbage Collector zajmuje się tylko zwalnianiem pamięci zajmowanej przez reference types. więcej o Garbage Collectorze można poczytać tutaj [under construction]
- struktury
- enumy
- typy proste - liczbowe, bool i char
- klasy
- interfejsy
- delegaty (więcej o delegatach tutaj [under construction])
- stringi (każdy string to obiekt klasy string)
- tablice (każda tablica dziedziczy z klasy System.Array)
class Program { static void Main(string[] args) { int valueType = 1; int valueTypeCopy = valueType; valueTypeCopy = 2; Console.WriteLine(valueType); //wypisze się 1, bo wartość valueType została niezmieniona, gdyż valueTypeCopy to kopia Console.ReadKey(); } }
A tak działa to dla reference types:
class NumberBox { public int Number; } class Program { static void Main(string[] args) { var referenceType = new NumberBox { Number = 1 }; var referenceTypeCopy = referenceType; referenceTypeCopy.Number = 2; Console.WriteLine(referenceType.Number); //wypisze się 2, bo referenceTypeCopy odnosi się do tego samego obiektu co referenceType Console.ReadKey(); } }
Trochę podstępnie zachowuje się string. Skoro to reference type, powinien działać tak samo jak każda inna klasa. Ale czy tak jest?
class Program { static void Main(string[] args) { string referenceType = "abc"; string referenceTypeCopy = referenceType; referenceTypeCopy = "def"; Console.WriteLine(referenceType); //wypisuje się "abc". Dlaczego? Console.ReadKey(); } }
Dzieje się tak, ponieważ w C# string jest immutable - a to oznacza, że jego wartość nie może zostać zmieniona po utworzeniu obiektu. Dlatego każda modyfikacja lub przypisanie stringa de facto tworzy nowy obiekt klasy string i przypisuje jego adres do starej referencji. W linijce w której do referenceTypeCopy przypisujemy referenceType tworzony jest nowy obiekt zajmujący inne miejsce w pamięci niż stary, dlatego zmiana treści referenceTypeCopy nie wpływa na treść referenceType. Więcej można poczytać o tym tutaj [under construction].
Ten przykład z karteczką z numerem oraz karteczką z koordynatami w bibliotece - genialny. Ciężko będzie to zapomnieć :)
OdpowiedzUsuń