== vs. Object.Equals (объект) в .NET - PullRequest
49 голосов
/ 22 сентября 2008

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

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

Ответы [ 9 ]

34 голосов
/ 22 сентября 2008
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
17 голосов
/ 22 сентября 2008

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

object.Equals метод

оператор ==

Операторы с перегрузкой

Рекомендации по переопределению равных () и оператора ==

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

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

9 голосов
/ 09 мая 2014

Microsoft говорит, что разработчики классов должны заставить == вести себя как можно ближе к Equals:

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

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


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

Если разработчик класса не переопределяет ==, то статический метод ищется во время компиляции в базовых классах. Если этот поиск достигает Object, то используется Object.==. Для классов это то же самое, что и ReferenceEquals.

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

OTOH, ps2goat подчеркивает, что использование == позволяет избежать исключения, если первый операнд равен нулю (поскольку == является статическим оператором). Это аргумент в пользу использования ==.


Удален спорный комментарий относительно ==


ОБНОВЛЕНИЕ Недавняя цитата из документа Microsoft от .Net 4.7.2, полученная в феврале 2019 года, показывает, что они все еще намерены вести себя одинаково:

Метод Object.Equals

Некоторые языки, такие как C # и Visual Basic, поддерживают перегрузку операторов. Когда тип перегружает оператор равенства, он также должен переопределить метод Equals (Object) для обеспечения той же функциональности. Обычно это достигается написанием метода Equals (Object) в терминах перегруженного оператора равенства, как в следующем примере.


ПРИМЕЧАНИЕ. См. Другие ответы о том, что == - статический метод, а Equals - метод экземпляра. Я не утверждаю, что поведение идентично; Я наблюдаю, что Microsoft рекомендует сделать их как можно более похожими.

5 голосов
/ 20 августа 2015

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

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().

1 голос
/ 26 февраля 2019

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

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

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

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

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

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

Простейшим примером этого являются строки в сравнении с типами StringBuilder. Переопределение строк ==, 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) точно так же.

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

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

1 голос
/ 22 сентября 2008

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

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

0 голосов
/ 18 мая 2015

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

* ** 1003 тысяча два * Пример
    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.
0 голосов
/ 26 августа 2010

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

0 голосов
/ 22 сентября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...