Dzisiaj będę się chwalił się swoją niewiedzą. Kilka dni temu kolega zadał mi pytanie, którym skutecznie mnie zagiął. Ponieważ uważam, że głupotą nie jest brak wiedzy co raczej udawanie, że się ją posiada, czym prędzej się Wam tym pytaniem chwalę.
Co zostanie wyświetlone po takim kodzie i dlaczego?
string string1 = "Test";
string string2 = string1;
string string3 = "Test";
Console.WriteLine(Equals(string1, string2)); // 1.
Console.WriteLine(Equals(string1, string3)); // 2.
Console.WriteLine(ReferenceEquals(string1, string2)); //3.
Console.WriteLine(ReferenceEquals(string1, string3)); //4.
Pierwsze i drugie porównanie pokażą oczywiście
true - Equals porównujący stringi o tej samej wartości zawsze pokaże
true
Drugi i trzeci pokażą również
true - tutaj był mój błąd. Uznałem, że skoro string nie jest typem referencyjnym to przy przepisaniu skopiuje on wartość, a nie przepisze referencję. W teorii tak powinno być, ale w tym momencie do gry wchodzi
Optymalizator Kompilacji. Ten byt czuwa nad tym, aby nasz program działał jak najszybciej maskując ile się da nasze błędy. W tym przypadku zauważył, że nie ma sensu tworzyć nie wiadomo ile obiektów, skoro i tak string ma przeładowaną metodę Equals i operatoror porównywania. Zamiast generować kolejne obiekty poprzypinał on referencje do jednego obiektu.
Optymalizator jest na tyle sprytny, że potrafi się obronić przed takimi trikami:
string string4 = string1 + String.Empty;
Console.WriteLine(ReferenceEquals(string1, string4));
Dla powyższego kodu również wyświetli true.
Dopiero coś takiego potrafi go wprawić w konsternację i każe mu wyświetlić upragniony
false:
string string5 = (string3 + "a").Replace("a", String.Empty);
Console.WriteLine(ReferenceEquals(string1, string5));
(Ukryj)
W odpowiedzi jest drobny błąd :) ponieważ String jest typem referencyjnym tylko jednocześnie jest immutable i ma przeładowane metody dzięki którym zachowuje się jak typ prosty. Przykład:
OdpowiedzUsuństring s1 = "Jeden";
string s2 = "Dwa";
s1 = s2;
//Poniżej dzieje się magia
s2= "Nowa wartość"
Oznacza to tyle, że przypisując wartość już do utworzonego stringa, tworzymy nowy obiekt i zwracamy do niego referencję. Zagadnieniem związanym z tym jest "interning", za który odpowiedzialny jest CLR. Przechowuje on hash tablicę, która zawiera wpis dla każdego literału i jego adresu w pamięci. W momencie przypisywania literału do typu string CLR sprawdza, czy taki literał już istnieje. W przeciwnym wypadku tworzy nowy wpis w tablicy. Jeżeli literały są takie same wtedy faktycznie wskazują na jeden obiekt. To tłumaczy działanie ReferenceEquals.
string3 + "a" tworzy nowy wpis w tablicy, metoda Replace też. Pod dodaniu String.Empty w tablicy dalej można znaleźć wpis :)
Odpowiedź trochę zagmatwana: "Pierwsze i drugie..." "Drugi i trzeci..." Gdzie jest czwarty? :P
True, true.
OdpowiedzUsuńMiałem dorzucić linka z erratą: http://msdn.microsoft.com/en-us/library/system.string.intern%28v=VS.71%29.aspx
Tutaj jest wytłumaczony intern.
Co do czwartego to jest to niedopatrzenie, oczywiście również zwróci false.
Podsumowując - dzięki za komentarz :)
Czwarty zwróci true. Wszystkie zwrócą true :)
OdpowiedzUsuńNo problem.
Hehe, dobra chyba będę musiał pisać niedługo nawet erraty do komentarzy, muszę się lepiej przyglądać temu co piszę przed wysłaniem. Oczywiście, że true.
OdpowiedzUsuńDobrze, że są ludzie, którzy czuwają ;)
Hehe, no stress :)
OdpowiedzUsuńCzekam na kolejną ciekawostkę :]