Пустая строка как особый случай?

Я читаю викторину Джона Скита, и я задавался вопросом, почему второй образец моей работы не будет работать, пока первый сделает.

Почему это дает true:

object x = new string("".ToArray());
object y = new string("".ToArray());
Console.WriteLine(x == y); //true

Но этого нет:

var k="k";
//string.intern(k); // doesn't help
object x = new string(k.ToArray());
object y = new string(k.ToArray());
Console.WriteLine(x == y); //false

Я использую fw 4.5 с vs2010.

К счастью, у меня также установлен vs2005, те же результаты:

enter image description here

Ответ 1

Вот сообщение в блоге Эрика Липперта, которое отвечает на ваш вопрос: String interning и String.Empty.

Он описывает аналогичную ситуацию:

object obj = "Int32";
string str1 = "Int32";
string str2 = typeof(int).Name;
Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true
Console.WriteLine(obj == str2); // false !?

Итак, идея состоит в том, что интернирование не означает, что у вас будет только один экземпляр конкретного string, даже если он интернирован. Только компилируемые литералы времени интернированы по умолчанию. Это означает, что следующий код печатает true:

var k1 = "k";
object k2 = "k";
Console.WriteLine(k1 == k2);

Но, если вы попытаетесь создать строку с "k" программным программным обеспечением во время выполнения, например. используя конструктор string(char[]), вызывая ToString() для объекта, используя StringBuilder и т.д., вы не получите интернированную строку по умолчанию. Этот символ печатает false,

var k1 = "k";
object k2 = new string("k".ToCharArray());
Console.WriteLine(k1 == k2);

Почему? Поскольку интернирование строк во время выполнения является дорогостоящим.

Нет такой вещи как бесплатный обед.

(...)

Короче говоря, в общем случае не стоит ставить все строки.

И о различном поведении с пустой строкой:

Некоторые версии среды выполнения .NET автоматически ставят пустую строку во время выполнения, некоторые не работают!

Ответ 2

Обратите внимание, что интернирование новых строк во втором блоке кода делает их равными.

var k="k";
object x = string.Intern(new string(k.ToArray()));
object y = string.Intern(new string(k.ToArray()));
Console.WriteLine(x == y); //true

Кажется, что он автоматически выполняет пустые строки, но непустые строки не интернированы, если они не выполняются явно (или они являются буквальными строками, которые всегда интернированы).

Я предполагаю, что да, пустые строки рассматриваются как особый случай и автоматически становятся интернированными, вероятно, потому, что проверка настолько тривиальна, что не добавляет никакого реального штрафа за производительность (мы можем смело сказать, что ЛЮБАЯ строка длина 0 - пустая строка и идентична любой другой пустой строке - все остальные строки требуют от нас поискать символы, а не только длину).

Ответ 3

Первый случай сравнивает 2 ссылки на один и тот же объект (String.Empty). Вызов operator== для 2 object переменных приводит к их сравнению по ссылке и дает true.

Второй случай создает 2 разных экземпляра строкового класса. Их сравнительное сравнение дает false

Если вы укажете тип string на x и y, во втором случае будет вызываться переопределение string.operator==, и сравнение дает true

Обратите внимание, что мы не имеем дело с интернацией строк непосредственно в обоих случаях. Строковые объекты, которые мы сравниваем, создаются с помощью конструктора string(char[]). По-видимому, этот конструктор предназначен для возврата значения поля String.Empty при вызове с пустым массивом в качестве аргумента.

Ответ, опубликованный MarcinJuraszek, ссылается на блог Lippert, в котором обсуждается интернирование строк. В этом сообщении в блоге обсуждается другой пример использования класса строки. Рассмотрите этот пример из предыдущего документа Lippert:

object obj = "";
string str1 = "";
string str2 = String.Empty;
Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true
Console.WriteLine(obj == str2); // sometimes true, sometimes false?!

Мы видим здесь, что присваивание из пустого литерала строки ("") не гарантирует получение ссылки на статическое поле readtonly System.String.Empty.

Посмотрим на IL для выражения object x = new string("".ToArray());:

IL_0001:  ldstr      ""
IL_0006:  call       !!0[] [System.Core]System.Linq.Enumerable::ToArray<char>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
IL_000b:  newobj     instance void [mscorlib]System.String::.ctor(char[])
IL_0010:  stloc.0

Интернирование может (или не обязательно) происходить на линии IL_0001. Независимо от того, интерполирован ли литерал или нет, метод ToArray() создает новый пустой массив, а String::.ctor(char[]) дает нам String.Empty.

То, что мы видим здесь, не является особым случаем String.Empty, а скорее является одним из побочных эффектов класса string, являющегося ссылочным типом и неизменным в одно и то же время. Существуют и другие неизменяемые типы фреймов, которые имеют предопределенные значения с аналогичной семантикой (например, DateTime.MinValue). Но насколько я знаю, такие типы фреймов определяются как struct в отличие от string, который является ссылочным типом. Типы значений - совершенно другая история... Не имеет смысла возвращать некоторый фиксированный предопределенный экземпляр типа из изменяемого класса конструктора (вызывающий код сможет изменить этот экземпляр и вызвать непредсказуемое поведение этого типа). Поэтому ссылочные типы, конструкторы которых не всегда возвращают новые экземпляры, могут существовать при условии, что эти типы являются неизменными. Я не знаю других таких типов в рамках, хотя, кроме string.

Ответ 4

Моя гипотеза - это то, почему первая дает true, а вторая дает false:

Первый результат my будет оптимизацией, возьмите следующий код

Enumerable.Empty<char>() == Enumerable.Empty<char>() // true

Итак, предположим, что метод ToArray возвращает Enumerable.Empty<char>(), когда строка пуста, это объясняет, почему первый результат дает true, а второй - нет, поскольку он выполняет контрольную проверку.

Ответ 5

Согласно http://msdn.microsoft.com/en-us/library/system.string.intern(v=vs.110).aspx

In the .NET Framework 3.5 Service Pack 1, the Intern method reverts to its behavior in the .NET Framework 1.0 and 1.1 with regard to interning the empty string...

...In the .NET Framework 1.0, .NET Framework 1.1, and .NET Framework 3.5 SP1, ~empty strings~ are equal

Это означает, что пустые строки по умолчанию интернированы, даже при построении из пустого массива, и поэтому равны.

Далее

The .NET Framework version 2.0 introduces the CompilationRelaxations.NoStringInterning enumeration member

Это, скорее всего, дает вам способ создать последовательный способ сравнения, хотя, как предлагает @BenM, вы предпочитаете явно использовать функцию Intern.

Учитывая возникший бокс, вы также можете использовать string.Equals вместо ==

Ответ 6

Я думаю, что это может быть причиной, по которой я отсылаю Jon Skeet Ответ о сравнении строк

Являются ли операторы string.Equals() и == одинаковыми?

        object x1 = new StringBuilder("").ToString().ToArray();
        object y1 = new StringBuilder("").ToString().ToArray();
        Console.WriteLine(x1 == y1); //true

        Console.WriteLine("Address x1:" + Get(x1));
        Console.WriteLine("Address y1:" + Get(y1));

        var k = "k";
        //string.intern(k); // doesn't help
        object x = new string(k.ToArray());
        object y = new string(k.ToArray());
        Console.WriteLine(x == y); //false

        Console.WriteLine("Address x:" + Get(x));
        Console.WriteLine("Address y:" + Get(y));

        Console.Read(); 

Выход

False
Address x1:0x2613E5
Address y1:0x2613E5
False
Address x:0x2613E5
Address y:0x2613E5

Ответ 7

Существует специальный случай, когда пустые строки всегда возвращают один и тот же объект, и поэтому, когда вы сравниваете, если объект в этом случае тот же, что и правда.

[Изменить]: в предыдущем коде использовался конструктор строк вместо объекта

object a = "s";
object b = "d";

a = ((string)a).Replace("s", "");
b = ((string)b).Replace("d", "");

Console.WriteLine(a == b);

object c = "sa";
object d = "da";

c = ((string)c).Replace("s", "");
d = ((string)d).Replace("d", "");

Console.WriteLine(c == d);

c = ((string)c).Replace("a", "");
d = ((string)d).Replace("a", "");

Console.WriteLine(c == d);

результат

True
False
True