== vs. Object.Equals(объект) в.NET
Поэтому, когда я был новичком в сравнении с новичком, которым я являюсь сейчас, я привык думать, что эти две вещи были синтаксическим сахаром друг для друга, то есть использование одного над другим было просто личным предпочтением. Со временем я пришел к выводу, что эти два не одно и то же, даже в реализации по умолчанию (см. Это и это). Чтобы еще больше запутать вопрос, каждый из них может быть переопределен / перегружен отдельно, чтобы иметь совершенно разные значения.
Это хорошо, каковы различия, и когда / почему вы должны использовать один над другим?
9 ответов
MSDN имеет четкие и надежные описания обеих вещей.
Рекомендации по переопределению 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 для конкретного равенства (действительно ли эти два аргумента на самом деле являются одним и тем же объектом?).
Редактировать: связанная статья Кевина Шеффилда лучше объясняет ценность и референтное равенство...
Чтобы ответить на это, мы должны описать четыре вида эквивалентности объектов:
Справочное равенство, object.ReferenceEquals (a, b): две переменные указывают на один и тот же точный объект в оперативной памяти. (Если бы это был C, обе переменные имели бы один и тот же точный указатель.)
Взаимозаменяемость, a == b: две переменные относятся к объектам, которые полностью взаимозаменяемы. Таким образом, когда a == b, Func(a,b) и Func(b,a) делают одно и то же.
Семантическое равенство, object.Equals (a, b): в этот точный момент времени два объекта означают одно и то же.
Равенство сущностей, 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.