Перегрузка операторов в общих методах

Этот фрагмент кода из 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 во втором

Другие вопросы по тегам