Перегрузка операторов в общих методах
Этот фрагмент кода из C# в глубине
static bool AreReferencesEqual<T>(T first, T second)
where T : class
{
return first == second;
}
static void Main()
{
string name = "Jon";
string intro1 = "My name is " + name;
string intro2 = "My name is " + name;
Console.WriteLine(intro1 == intro2);
Console.WriteLine(AreReferencesEqual(intro1, intro2));
}
Вывод приведенного выше фрагмента кода:
True
False
Когда основной метод изменен на
static void Main()
{
string intro1 = "My name is Jon";
string intro2 = "My name is Jon";
Console.WriteLine(intro1 == intro2);
Console.WriteLine(AreReferencesEqual(intro1, intro2));
}
Вывод приведенного выше фрагмента кода:
True
True
Я не могу понять, почему?
РЕДАКТИРОВАТЬ: Как только вы понимаете интернирование строк, следующие вопросы не применяются.
Как параметры получаются при универсальном методе AreReferencesEqual
во втором фрагменте кода?
Что меняет тип строки, когда он объединяется, чтобы оператор == не вызывал перегруженный метод Equals типа String?
3 ответа
В случае строк вы, вероятно, не собираетесь использовать равенство ссылок. Чтобы получить доступ к равенству и неравенству в общих методах, лучше всего сделать это:
EqualityComparer<T>.Default.Equals(x,y); // for equality
Comparer<T>.Default.Compare(x,y); // for inequality
т.е.
static bool AreValuesEqual<T>(T first, T second)
where T : class
{
return EqualityComparer<T>.Default.Equals(first,second);
}
Это все еще использует перегруженный Equals
, но обрабатывает нулями и т. д. тоже. Для неравенства это обрабатывает нули, и оба IComparable<T>
а также IComparable
,
Для других операторов см. MiscUtil.
Пере вопрос; в случае:
string intro1 = "My name is Jon";
string intro2 = "My name is Jon";
Console.WriteLine(intro1 == intro2);
Console.WriteLine(AreReferencesEqual(intro1, intro2));
Ты получаешь true
, true
потому что компилятор и среда выполнения спроектированы так, чтобы быть эффективными со строками; все литералы, которые вы используете, являются "интернированными", и каждый раз в вашем домене приложений используется один и тот же экземпляр. Компилятор (а не среда выполнения) также делает concat, если это возможно, т.е.
string intro1 = "My name is " + "Jon";
string intro2 = "My name is " + "Jon";
Console.WriteLine(intro1 == intro2);
Console.WriteLine(AreReferencesEqual(intro1, intro2));
точно такой же код, как и в предыдущем примере. Там нет никакой разницы вообще. Однако, если вы заставляете его объединять строки во время выполнения, он предполагает, что они могут быть недолговечными, поэтому они не используются и не используются повторно. Итак, по делу:
string name = "Jon";
string intro1 = "My name is " + name;
string intro2 = "My name is " + name;
Console.WriteLine(intro1 == intro2);
Console.WriteLine(AreReferencesEqual(intro1, intro2));
у вас есть 4 строки; "Джон" (интернированный), "Меня зовут" (интернированный) и два разных экземпляра "Меня зовут Джон". следовательно ==
возвращает true, а равенство ссылок возвращает false. Но ценность-равенство (EqualityComparer<T>.Default
) все равно вернет истину.
Узнал новое сегодня.
Я думаю, что Джон сказал в одном из вопросов, я попытался ответить.
Когда вы строите строку с использованием конкатенации, == вернет true для 2 строк с совпадающим значением, но они не указывают на одну и ту же ссылку (что, как я думал, должно быть из-за интернирования строк. Джон указал, что интернирование строк работает для констант или литералы).
В общей версии он вызывает object.ReferenceEquals (который отличается от ==. В случае строки == выполняет сравнение значений).
В результате конкатенированная версия возвращает false, тогда как константная (литеральная строка) версия возвращает true.
РЕДАКТИРОВАТЬ: Я думаю, Джон должен быть рядом, чтобы объяснить это гораздо лучше:)
Ленивый я, я купил книгу, но еще не начал ее.:(
Это не имеет ничего общего с универсальным методом, но создание экземпляров строк
в первой версии main у вас есть:
string name = "Jon";
string intro1 = "My name is " + name;
string intro2 = "My name is " + name;
который создает 4 строки. Две из которых являются константами времени компиляции, а именно "Jon" и "My name is", однако при инициализации intro1 и intro2 компилятор не может сказать, что name всегда является jon, и разрешает значение времени выполнения, создавая новую строку для каждого из intro1 и intro2.
во второй версии
string intro1 = "My name is Jon";
string intro2 = "My name is Jon";
у вас есть только одна строка, и это постоянная времени компиляции: "Меня зовут Джон", и вы присваиваете эту строку как intro1, так и intro2, и поэтому
AreReferencesEqual(intro1, intro2)
возвращает false в первом случае и true во втором