Подтвердить что ты не робот

Сравнение строк == работает только потому, что строки неизменяемы?

У меня была мысль прежде, сравнивая две строки с их переменными:

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, мы фактически сравниваем ссылки и не значения? Я изначально думал, что это был продукт синтаксического сахара, но был ли я неправильным?

Любые неточности с тем, что я написал?

4b9b3361

Ответ 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 == сравнивает значения строк.