Сравнение строк == работает только потому, что строки неизменяемы?
У меня была мысль прежде, сравнивая две строки с их переменными:
string str1 = "foofoo";
string strFoo = "foo";
string str2 = strFoo + strFoo;
// Even thought str1 and str2 reference 2 different
//objects the following assertion is true.
Debug.Assert(str1 == str2);
Это чисто потому, что среда выполнения .NET признает, что строковое значение одно и то же, и поскольку строки неизменяемы, ссылка на str2
равна значению str1
?
Итак, когда мы делаем str1 == str2
, мы фактически сравниваем ссылки и не значения? Я изначально думал, что это был продукт синтаксического сахара, но был ли я неправильным?
Любые неточности с тем, что я написал?
Ответы
Ответ 1
Если мы посмотрим на jitted-код, мы увидим, что str2
собирается с использованием String.Concat
и что на самом деле это не та же ссылка, что и str1
. Мы также увидим, что сравнение выполняется с помощью Equals
. Другими словами, утверждение проходит, поскольку строки содержат одни и те же символы.
Этот код
static void Main(string[] args)
{
string str1 = "foofoo";
string strFoo = "foo";
string str2 = strFoo + strFoo;
Console.WriteLine(str1 == str2);
Debugger.Break();
}
имеет значение (прокрутите страницу в сторону, чтобы увидеть комментарии)
C:\dev\sandbox\cs-console\Program.cs @ 22:
00340070 55 push ebp
00340071 8bec mov ebp,esp
00340073 56 push esi
00340074 8b3530206003 mov esi,dword ptr ds:[3602030h] ("foofoo") <-- Note address of "foofoo"
C:\dev\sandbox\cs-console\Program.cs @ 23:
0034007a 8b0d34206003 mov ecx,dword ptr ds:[3602034h] ("foo") <-- Note different address for "foo"
C:\dev\sandbox\cs-console\Program.cs @ 24:
00340080 8bd1 mov edx,ecx
00340082 e81977fe6c call mscorlib_ni+0x2b77a0 (6d3277a0) (System.String.Concat(System.String, System.String), mdToken: 0600035f) <-- Call String.Concat to assemble str2
00340087 8bd0 mov edx,eax
00340089 8bce mov ecx,esi
0034008b e870ebfd6c call mscorlib_ni+0x2aec00 (6d31ec00) (System.String.Equals(System.String, System.String), mdToken: 060002d2) <-- Compare using String.Equals
00340090 0fb6f0 movzx esi,al
00340093 e83870f86c call mscorlib_ni+0x2570d0 (6d2c70d0) (System.Console.get_Out(), mdToken: 060008fd)
00340098 8bc8 mov ecx,eax
0034009a 8bd6 mov edx,esi
0034009c 8b01 mov eax,dword ptr [ecx]
0034009e 8b4038 mov eax,dword ptr [eax+38h]
003400a1 ff5010 call dword ptr [eax+10h]
C:\dev\sandbox\cs-console\Program.cs @ 28:
003400a4 e87775596d call mscorlib_ni+0x867620 (6d8d7620) (System.Diagnostics.Debugger.Break(), mdToken: 0600239a)
C:\dev\sandbox\cs-console\Program.cs @ 29:
>>> 003400a9 5e pop esi
003400aa 5d pop ebp
003400ab c3 ret
Ответ 2
Ответ в С# Spec §7.10.7
Операторы равенства строк сравнивают строковые значения, а не строку Рекомендации. Когда два отдельных экземпляра строки содержат то же самое последовательности символов, значения строк равны, но ссылки разные. Как описано в п. 7.10.6, ссылочный тип операторы равенства могут использоваться для сравнения ссылок на строки вместо строковые значения.
Ответ 3
Нет.
== работает, потому что класс String перегружает оператор ==, чтобы он был эквивалентен методу Equals.
От рефлектора:
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
public static bool operator ==(string a, string b)
{
return Equals(a, b);
}
Ответ 4
Собственно, String.Equals
сначала проверяет, является ли это той же ссылкой и если не сравнивает содержимое.
Ответ 5
Это чисто потому, что среда выполнения .NET признает, что строковое значение одно и то же, и потому строки неизменяемы, делает ссылку на str2 равной ссылке str1?
Нет. FIrst, это потому, что str1 и str2 одинаковы - они являются одной и той же строкой, потому что компилятор может оптимизировать это. strFoo + strFoo - это константа времени компиляции itendical для str1. Поскольку строки имеют значение INTERNED в классах, они используют одну и ту же строку.
Во-вторых, строка OVERRIDES tthe ==. CHeck исходный код из справочных источников, доступных в Интернете в течение некоторого времени.
Ответ 6
Оператор ссылочного равенства ==
может быть переопределен; и в случае System.String
он переопределяется для использования поведения равенства по значению. Для истинного равенства ссылок вы можете использовать метод Object.ReferenceEquals()
, который нельзя переопределить.
Ответ 7
В том порядке, в котором ваш код попадает в него...
==
переопределяется. Это означает, что вместо "abc" == "ab" + "c"
вызова стандартного ==
для ссылочных типов (который сравнивает ссылки, а не значения) он вызывает string.Equals(a, b)
.
Теперь это делает следующее:
- Если они действительно являются одной и той же ссылкой, верните true.
- Если оба значения равны нулю, верните false (мы бы уже вернули true, если оба они равны нулю).
- если две разные длины, верните false,
- Выполняйте оптимизированный цикл через одну строку, сравнивая ее char -for- char с остальными (фактически int-for-int рассматривается как два блока int в памяти, что является одной из оптимизаций), Если он достигнет конца без несоответствия, верните true, иначе верните false.
Другими словами, он начинается с чего-то вроде:
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;
}
За исключением того, что шаг 4 представляет собой версию на основе указателя с развернутым циклом, который должен, следовательно, идеально быть быстрее. Я не буду показывать это, потому что хочу говорить об общей логике.
Имеются значительные сокращения. Первый - на этапе 1. Поскольку равенство рефлексивно (идентичность влечет за собой равенство, a == a
), то мы можем вернуть значение true в наносекундах даже для строки размером в несколько МБ, если сравнивать с ней.
Шаг 2 не является сокращенным, потому что его условие должно быть проверено, но обратите внимание, что, поскольку мы уже вернемся к true для (string)null == (string)null
, нам не нужна другая ветка. Таким образом, порядок вызова ориентирован на быстрый результат.
Шаг 3 допускает две вещи. Это и короткое замыкание на строках разной длины (всегда ложно), и означает, что нельзя случайно выстрелить мимо конца одной из строк, сравниваемых на шаге 4.
Обратите внимание, что это не относится к другим сравнениям строк, так как, например, WEISSBIER
и weißbier
- разные длины, но одно и то же слово в различной капитализации, поэтому нечувствительное к регистру сравнение не может использовать шаг 3. Все сравнения сравнений могут выполнять шаги 1 и 2, поскольку используемые правила всегда сохраняются, поэтому вы должны использовать их в ваш собственный, только некоторые могут сделать шаг 3.
Следовательно, хотя вы ошибаетесь в предположении, что это скорее ссылки, а не сравниваемые значения, верно, что ссылки сравниваются сначала как очень важный короткий отрезок. Обратите внимание также, что интернированные строки (строки, помещенные в внутренний пул путем компиляции или вызванный string.Intern
), будут часто запускать этот короткий отрезок. Это будет иметь место в коде в вашем примере, поскольку компилятор будет использовать одну и ту же ссылку в каждом случае.
Если вы знаете, что строка была интернирована, вы можете зависеть от нее (просто выполните тестовый тест равенства), но даже если вы не знаете наверняка, что вы можете извлечь из нее выгоду (контрольный тест равенства будет сокращен, по крайней мере, некоторым времени).
Если у вас есть куча строк, где вам нужно часто проверять некоторые из них друг против друга, но вы не хотите продлевать срок их службы в памяти столько, сколько делает интернирование, тогда вы можете использовать XmlNameTable или LockFreeAtomizer (вскоре будет переименован ThreadSafeAtomizer и doc переместился в http://hackcraft.github.com/Ariadne/documentation/html/T_Ariadne_ThreadSafeAtomizer_1.htm - вначале должен был быть назван как функция, а не детали реализации).
Первый используется внутри XmlTextReader
и, следовательно, множеством остальных System.Xml
и может использоваться и другим кодом. Последнее, что я написал, потому что мне нужна аналогичная идея, которая была безопасной для одновременных вызовов, для разных типов и где я мог бы переопределить сравнение равенства.
В любом случае, если вы поместите 50 различных строк, которые все "abc" в него, вы получите единственную ссылку "abc", позволяющую другим собрать мусор. Если вы знаете, что это произошло, вы можете зависеть только от ReferenceEquals
, и если вы не уверены, вы все равно выиграете от короткого замыкания, когда это будет так.
Ответ 8
В соответствии с msdn (http://msdn.microsoft.com/en-us/library/53k8ybth.aspx):
Для предопределенных типов значений оператор равенства (==) возвращает true, если значения его операндов равны, в противном случае - false. Для ссылочных типов, отличных от строки, == возвращает true, если два операнда относятся к одному и тому же объекту. Для типа string == сравнивает значения строк.