== vs. Object.Equals(объект) в.NET

Поэтому, когда я был новичком в сравнении с новичком, которым я являюсь сейчас, я привык думать, что эти две вещи были синтаксическим сахаром друг для друга, то есть использование одного над другим было просто личным предпочтением. Со временем я пришел к выводу, что эти два не одно и то же, даже в реализации по умолчанию (см. Это и это). Чтобы еще больше запутать вопрос, каждый из них может быть переопределен / перегружен отдельно, чтобы иметь совершенно разные значения.

Это хорошо, каковы различия, и когда / почему вы должны использовать один над другим?

9 ответов

Решение

MSDN имеет четкие и надежные описания обеих вещей.

метод object.Equals

оператор ==

Перегружаемые операторы

Рекомендации по переопределению Equals() и Operator ==

Это хорошо, каковы различия, и когда / почему вы должны использовать один над другим?

Как это может быть "хорошо" или "плохо"? Один - метод, другой - оператор. Если ссылочного равенства недостаточно, перегрузите их, иначе оставьте их как есть. Для примитивных типов они просто работают из коробки.

string x = "hello";
string y = String.Copy(x);
string z = "hello";

Чтобы проверить, если x указывает на тот же объект, что и y:

(object)x == (object)y  // false
x.ReferenceEquals(y)    // false
x.ReferenceEquals(z)    // true (because x and z are both constants they
                        //       will point to the same location in memory)

Чтобы проверить, если x имеет то же строковое значение, что и y:

x == y        // true
x == z        // true
x.Equals(y)   // true
y == "hello"  // true

Обратите внимание, что это отличается от Java. В Java == оператор не перегружен, поэтому распространенная ошибка в Java:

y == "hello"  // false (y is not the same object as "hello")

Для сравнения строк в Java нужно всегда использовать .equals()

y.equals("hello")  // true

Учитывая текущее заявление Microsoft об операторах равенства == а также !=, вывод: == должен быть просто синтаксическим сахаром для Object.Equals():

НЕОБХОДИМО убедиться, что Object.Equals и операторы равенства имеют одинаковую семантику

от http://msdn.microsoft.com/en-us/library/vstudio/7h9bszxx(v=vs.110).aspx

Оглядываясь назад, можно сказать, что они отличаются друг от друга, а не просто сахаром.

Если вы хотите быть уверены, что вы получаете сравнение IDENTITY (при сравнении ссылок), используйте ReferenceEquals вместо.

К сожалению, обработка == настолько противоречиво, что я обычно избегаю этого, манипулируя чужими пользовательскими классами, и просто использую менее читаемые Equals(a, b) или же ReferenceEquals(a, b) в зависимости от того, какой смысл я хочу.

ИМХО, лучше бы людям не реализовывать == а также != совсем. Просто позвольте.Net по умолчанию Equals а также ! Equals и реализовать Equals по мере необходимости.

Если у кого-то есть другие рассуждения, я бы хотел это услышать.

(И да, это действительно сбивает с толку, учитывая, что Java существовала первой и использует == означать ReferenceEquals, Но уже слишком поздно, чтобы изменить.Net так себя вести. И у нас есть собственное заявление Microsoft на этот счет в приведенной выше ссылке.)

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

dotnetfiddle: https://dotnetfiddle.net/gESLzO

Код скрипки:

    Object a = null;
    Object b = new Object();

    // Ex 1
    Console.WriteLine(a == b);
    // Ex 2
    Console.WriteLine(b == a);

    // Ex 3     
    Console.WriteLine(b.Equals(a));
    // Ex 4
    Console.WriteLine(a.Equals(b));

Первые 3 примера WriteLine будут работать, но четвертый выдает исключение. 1 и 2 использовать ==, который является статическим методом, который не требует создания объекта.

Пример 3 работает, потому что b создается экземпляр.

Пример 4 терпит неудачу, потому что a является nullи, следовательно, метод не может быть вызван для нулевого объекта.

Поскольку я пытаюсь кодировать как можно ленивее, я использую ==особенно при работе со сценариями, где любой объект (или оба) может быть нулевым. Если бы я этого не сделал, мне пришлось бы сначала сделать нулевую проверку, прежде чем я смогу позвонить .Equals(),

Мое понимание использования обоих было следующим: use == для концептуального равенства (в контексте, означают ли эти два аргумента одно и то же?) И.Equals для конкретного равенства (действительно ли эти два аргумента на самом деле являются одним и тем же объектом?).

Редактировать: связанная статья Кевина Шеффилда лучше объясняет ценность и референтное равенство...

Чтобы ответить на это, мы должны описать четыре вида эквивалентности объектов:

  1. Справочное равенство, object.ReferenceEquals (a, b): две переменные указывают на один и тот же точный объект в оперативной памяти. (Если бы это был C, обе переменные имели бы один и тот же точный указатель.)

  2. Взаимозаменяемость, a == b: две переменные относятся к объектам, которые полностью взаимозаменяемы. Таким образом, когда a == b, Func(a,b) и Func(b,a) делают одно и то же.

  3. Семантическое равенство, object.Equals (a, b): в этот точный момент времени два объекта означают одно и то же.

  4. Равенство сущностей, a.Id == b.Id: два объекта ссылаются на одну и ту же сущность, например на строку базы данных, но не обязательно должны иметь одинаковое содержимое.

Как программист, при работе с объектом известного типа вам необходимо понимать тип эквивалентности, который подходит вашей бизнес-логике в конкретный момент кода, в котором вы находитесь.

Простейший пример этого - строки против типов StringBuilder. String overrides ==, StringBuilder не делает:

var aaa1 = "aaa";
var aaa2 = $"{'a'}{'a'}{'a'}";
var bbb = "bbb";

// False because aaa1 and aaa2 are completely different objects with different locations in RAM
Console.WriteLine($"Object.ReferenceEquals(aaa1, aaa2): {Object.ReferenceEquals(aaa1, aaa2)}");

// True because aaa1 and aaa2 are completely interchangable
Console.WriteLine($"aaa1 == aaa2: {aaa1 == aaa2}");             // True
Console.WriteLine($"aaa1.Equals(aaa2): {aaa1.Equals(aaa2)}");   // True
Console.WriteLine($"aaa1 == bbb: {aaa1 == bbb}");               // False
Console.WriteLine($"aaa1.Equals(bbb): {aaa1.Equals(bbb)}");     // False

// Won't compile
// This is why string can override ==, you can not modify a string object once it is allocated
//aaa1[0] = 'd';

// aaaUpdated and aaa1 point to the same exact object in RAM
var aaaUpdated = aaa1;
Console.WriteLine($"Object.ReferenceEquals(aaa1, aaaUpdated): {Object.ReferenceEquals(aaa1, aaaUpdated)}"); // True

// aaaUpdated is a new string, aaa1 is unmodified
aaaUpdated += 'c';
Console.WriteLine($"Object.ReferenceEquals(aaa1, aaaUpdated): {Object.ReferenceEquals(aaa1, aaaUpdated)}"); // False

var aaaBuilder1 = new StringBuilder("aaa");
var aaaBuilder2 = new StringBuilder("aaa");

// False, because both string builders are different objects
Console.WriteLine($"Object.ReferenceEquals(aaaBuider1, aaaBuider2): {Object.ReferenceEquals(aaa1, aaa2)}");

// Even though both string builders have the same contents, they are not interchangable
// Thus, == is false
Console.WriteLine($"aaaBuider1 == aaaBuilder2: {aaaBuilder1 == aaaBuilder2}");

// But, because they both have "aaa" at this exact moment in time, Equals returns true
Console.WriteLine($"aaaBuider1.Equals(aaaBuilder2): {aaaBuilder1.Equals(aaaBuilder2)}");

// Modifying the contents of the string builders changes the strings, and thus
// Equals returns false
aaaBuilder1.Append('e');
aaaBuilder2.Append('f');
Console.WriteLine($"aaaBuider1.Equals(aaaBuilder2): {aaaBuilder1.Equals(aaaBuilder2)}");

Чтобы получить более подробную информацию, мы можем работать в обратном направлении, начиная с равенства сущностей. В случае равенства сущностей, свойства сущности могут изменяться со временем, но первичный ключ сущности никогда не меняется. Это можно продемонстрировать с помощью псевдокода:

// Hold the current user object in a variable
var originalUser = database.GetUser(123);

// Update the user’s name
database.UpdateUserName(123, user.Name + "son");

var updatedUser = database.GetUser(123);

Console.WriteLine(originalUser.Id == updatedUser.Id); // True, both objects refer to the same entity
Console.WriteLine(Object.Equals(originalUser, updatedUser); // False, the name property is different

Переходя к семантическому равенству, пример немного меняется:

var originalUser = new User() { Name = "George" };
var updatedUser = new User() { Name = "George" };

Console.WriteLine(Object.Equals(originalUser, updatedUser); // True, the objects have the same contents
Console.WriteLine(originalUser == updatedUser); // User doesn’t define ==, False

updatedUser.Name = "Paul";

Console.WriteLine(Object.Equals(originalUser, updatedUser); // False, the name property is different

Как насчет взаимозаменяемости? (переопределение ==) Это сложнее. Давайте построим на приведенном выше примере немного:

var originalUser = new User() { Name = "George" };
var updatedUser = new User() { Name = "George" };
Console.WriteLine(Object.Equals(originalUser, updatedUser); // True, the objects have the same contents

// Does this change updatedUser? We don’t know
DoSomethingWith(updatedUser);

// Are the following equivalent?
// SomeMethod(originalUser, updatedUser);
// SomeMethod(updatedUser, originalUser);

В приведенном выше примере DoSomethingWithUser(updatedUser) может изменить updatedUser. Таким образом, мы больше не можем гарантировать, что объекты originalUser и updatedUser являются "Равными". Вот почему пользователь не переопределяет ==.

Хороший пример того, когда переопределять == - это неизменные объекты. Неизменяемый объект - это объект, публично видимое состояние (свойства) которого никогда не меняются. Все видимое состояние должно быть установлено в конструкторе объекта. (Таким образом, все свойства доступны только для чтения.)

var originalImmutableUser = new ImmutableUser(name: "George");
var secondImmutableUser = new ImmutableUser(name: "George");

Console.WriteLine(Object.Equals(originalImmutableUser, secondImmutableUser); // True, the objects have the same contents
Console.WriteLine(originalImmutableUser == secondImmutableUser); // ImmutableUser defines ==, True

// Won’t compile because ImmutableUser has no setters
secondImmutableUser.Name = "Paul";

// But this does compile
var updatedImmutableUser = secondImmutableUser.SetName("Paul"); // Returns a copy of secondImmutableUser with Name changed to Paul.

Console.WriteLine(object.ReferenceEquals(updatedImmutableUser, secondImmutableUser)); // False, because updatedImmutableUser is a different object in a different location in RAM

// These two calls are equivalent because the internal state of an ImmutableUser can never change
DoSomethingWith(originalImmutableUser, secondImmutableUser);
DoSomethingWith(secondImmutableUser, originalImmutableUser);

Вы должны переопределить == с изменяемым объектом? (То есть объект, чье внутреннее состояние может измениться?) Наверное, нет. Вам потребуется создать довольно сложную систему событий, чтобы поддерживать взаимозаменяемость.

В общем, я работаю с большим количеством кода, который использует неизменяемые объекты, поэтому я переопределяю ==, потому что он более читабелен, чем object.Equals. Когда я работаю с изменяемыми объектами, я не переопределяю == и полагаюсь на object.Equals. Программист обязан знать, являются ли объекты, с которыми они работают, изменяемыми или нет, потому что знание того, может ли состояние чего-либо измениться, должно влиять на то, как вы разрабатываете свой код.

Реализация по умолчанию == - это object.ReferenceEquals, потому что с изменяемыми объектами взаимозаменяемость гарантируется только тогда, когда переменные указывают на один и тот же точный объект в оперативной памяти. Даже если объекты имеют одинаковое содержимое в данный момент времени (Equals возвращает true), нет гарантии, что объекты будут продолжать оставаться равными; таким образом, объекты не являются взаимозаменяемыми. Таким образом, при работе с изменяемым объектом, который не переопределяет ==, реализация по умолчанию == работает, потому что если a == b, это один и тот же объект, а SomeFunc(a, b) и SomeFunc(b, a) точно так же.

Кроме того, если класс не определяет эквивалентность (например, представьте себе соединение с базой данных и открытый дескриптор файла, ect,), то реализация по умолчанию == и Equals возвращается к ссылочному равенству, поскольку две переменные типа подключения к базе данных дескриптор открытого файла, т. д., равны, только если они являются точным экземпляром подключения к базе данных, дескриптор открытого файла, т. д. Равенство сущностей может иметь смысл в бизнес-логике, которая должна знать, что два разных подключения к базе данных ссылаются на одну и ту же базу данных или что два разных файловых дескриптора ссылаются на один и тот же файл на диске.

Теперь для моего мыльного момента. На мой взгляд, C# обрабатывает эту тему в замешательстве. == должно быть для семантического равенства вместо метода Equals. Должен быть другой оператор, например ===, для взаимозаменяемости и, возможно, другой оператор, ====, для ссылочного равенства. Таким образом, кто-то, кто является новичком и / или пишет CRUD-приложения, должен понимать только ==, а не более тонкие детали взаимозаменяемости и ссылочного равенства.

Вы можете использовать.Equals, так как кто-то может прийти позже и перегрузить их для вашего класса.

Два наиболее часто используемых типа, String и Int32, реализуют оба оператора ==() и Equals() как равенство значений (вместо ссылочного равенства). Я думаю, что можно рассмотреть эти два определяющих примера, поэтому я пришел к выводу, что оба имеют одинаковое значение. Если Microsoft заявляет иначе, я думаю, что они намеренно вызывают путаницу.

Оператор == и Equals() одинаковы, в то время как мы сравниваем значения вместо ссылок. Вывод обоих одинаков, см. Пример ниже.

пример

    static void Main()
    {
        string x = " hello";
        string y = " hello";
        string z = string.Copy(x);
        if (x == y)
        {
            Console.WriteLine("== Operator");
        }
        if(x.Equals(y))
        {
            Console.WriteLine("Equals() Function Call");
        }
        if (x == z)
        {
            Console.WriteLine("== Operator while coping a string to another.");
        }
        if (x.Equals(y))
        {
            Console.WriteLine("Equals() Function Call while coping a string to another.");
        }
    }

Выход:

  == Operator
  Equals() Function Call
  == Operator while coping a string to another.
  Equals() Function Call while coping a string to another.
Другие вопросы по тегам