W porządku, w którym kod trafia go ...
==
jest przesłonięte. Oznacza to, że zamiast "abc" == "ab" + "c"
wywoływanie domyślnego ==
dla typów odniesienia (które porównuje odniesienia i nie wartości) wywołuje string.Equals(a, b)
.
Teraz ten wykonuje następujące operacje:
- Jeśli te dwie rzeczy są rzeczywiście takie same odniesienia, return true.
- Jeśli jedna z nich ma wartość NULL, zwracana jest wartość false (wartość true byłaby zwrócona powyżej, gdyby oba miały wartość NULL).
- jeśli dwie mają inną długość, return false;
- Wykonaj zoptymalizowany cykl za pomocą jednego ciągu, porównując go char-for-char z resztą (faktycznie int-for-int, jak to widać w dwóch blokach ints w pamięci, co jest jedną z wymaganych optymalizacji). Jeśli dojdzie do końca bez niezgodności, to zwróci true, w przeciwnym razie zwróci false.
Innymi słowy, zaczyna się coś takiego:
public static bool ==(string x, string y)
{
//step 1:
if(ReferenceEquals(x, y))
return true;
//step 2:
if(ReferenceEquals(x, null) || ReferenceEquals(y, null))
return false;
//step 3;
int len = x.Length;
if(len != y.Length)
return false;
//step 4:
for(int i = 0; i != len; ++i)
if(x[i] != y[i])
return false;
return true;
}
z wyjątkiem tego kroku 4 jest wersja wskaźnika opartego o rozwiniętej pętli, które powinny zatem być idealnie szybciej. Nie pokażę tego, ponieważ chcę mówić o ogólnej logice.
Istnieją znaczące skróty. Pierwsza jest w kroku 1. Ponieważ równość jest odruchowa (tożsamość pociąga za sobą równość, a == a
), wówczas możemy zwrócić wartość true w nanosekundach nawet dla ciągu o wielkości kilku MB, jeśli porównać go z samym sobą.
Krok 2 nie jest skrótem, ponieważ jest to warunek, który musi zostać przetestowany, ale pamiętaj, że ponieważ powróciliśmy już do wersji (string)null == (string)null
, nie potrzebujemy kolejnego oddziału. Tak więc kolejność połączeń jest nastawiona na szybki wynik.
Krok 3 pozwala na dwie rzeczy. Oba skróty ciągi o różnej długości (zawsze fałszywe) i oznacza, że nie można przypadkowo strzelać poza koniec jednego z ciągów porównywanych w kroku 4.
Należy zauważyć, że tak nie jest w przypadku innych porównań łańcuchów , ponieważ np WEISSBIER
i weißbier
mają różną długość, ale to samo słowo z różną wielkością liter, więc porównywanie bez rozróżniania wielkości liter nie może użyć kroku 3. Wszystkie porównania równości mogą wykonywać etapy 1 i 2, ponieważ używane reguły zawsze się trzymają, więc powinieneś używać ich we własnym, tylko niektórzy mogą zrobić krok 3.
Dlatego też, podczas gdy mylisz się sugerując, że są to odniesienia, a nie wartości, które są porównywane, prawdą jest, że odniesienia są najpierw porównywane jako bardzo znaczące skróty. Zauważ też, że internowane ciągi (łańcuchy umieszczone w puli internowanej przez kompilację lub wywołane przez string.Intern
) będą często wyzwalać to skrótowe. Tak będzie w przypadku kodu w twoim przykładzie, ponieważ kompilator użyje tego samego odniesienia w każdym przypadku.
Jeśli wiesz, że struna została internowana, możesz na tym polegać (po prostu wykonaj test równości referencyjnej), ale nawet jeśli nie wiesz na pewno, możesz odnieść z tego korzyść (test równości skrótu co najmniej trochę czasu).
Jeśli masz kilka ciągów, w których często chcesz testować niektóre z nich, ale nie chcesz przedłużyć ich życia w pamięci tak, jak robi to interning, możesz użyć XmlNameTable lub LockFreeAtomizer (wkrótce zostanie zmieniona nazwa ThreadSafeAtomizer, a dokument przeniesiony do http://hackcraft.github.com/Ariadne/documentation/html/T_Ariadne_ThreadSafeAtomizer_1.htm - powinien zostać nazwany dla funkcji, a nie tylko szczegółów implementacji).
To pierwsze jest używane wewnętrznie przez XmlTextReader
, a przez to do reszty System.Xml
i może być używane również przez inny kod. Ten ostatni napisałem, ponieważ chciałem podobny pomysł, który byłby bezpieczny dla jednoczesnych połączeń, dla różnych typów i gdzie mógłbym zastąpić porównanie równości.
W obu przypadkach, jeśli umieścisz w nim 50 różnych ciągów, które są wszystkie "abc", otrzymasz pojedyncze odwołanie "abc", pozwalając innym zebrać śmieci. Jeśli wiesz, że tak się stało, możesz polegać wyłącznie na ReferenceEquals
, a jeśli nie masz pewności, nadal będziesz korzystać z skrótu, gdy tak się stanie.
@EricJ. Ale jeśli mają ten sam adres pamięci, wynika to z tego, że ** must ** mają tę samą zawartość (jest to jednak * ta sama instancja). – Yuck
@Yuck - Tylko jeśli interning jest częścią specyfikacji, a nie tylko szczegółem implementacji. Ponadto ciągi w oddzielnych domenach aplikacji mogą być równe i mieć różne adresy. – psr
@psr Właśnie, dlatego sprawdzanie warunkowe. Jeśli odniesienie jest takie samo, to gotowe - to wszystko. W przeciwnym razie musisz porównać zawartość każdej zmiennej, aby ustalić logiczną równość. – Yuck