Вопрос по .net – == vs. Object.Equals (объект) в .NET

49

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

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

Ваш Ответ

9   ответов
0

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

Но разве это не хорошо?
Да, я думаю, что хотел сказать это наоборот.
5

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

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

Обратите внимание, что это также происходит со строками. И, конечно, оператор может быть переопределен, но суть этого ответа состоит в том, что операторы являются статическими и не требуют ненулевых экземпляров для любого операнда.
34
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
Для строкового оператора == сравнивает обе строки по содержимому. Но это не относится к другим ссылочным типам
Я хочу подчеркнуть то, что Vaysage только что сказал: используя & quot; строку & quot; в качестве примераmisleading (или хотя бы неполный). Это показывает, как работают строки. Но строкаspecial case, Чтобы этот ответ был полным, противопоставьтеstring с: (а) одним символом, (б) массивом символов, (в)struct содержащий несколько символьных полей, (d)class содержащий несколько символьных полей. Может быть, даже нужно показать (е)class содержащийstruct поле или содержащийcharacter array поле. Затем выполняйте различные задания, покажите, когда результат ещеtrue.
-1

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

Example

    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.");
        }
    }

Output:

  == Operator
  Equals() Function Call
  == Operator while coping a string to another.
  Equals() Function Call while coping a string to another.
-1

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

В .net типы, которые переопределяют операторы равенства / неравенства, делают это для наложения значения равенства, но C # добавляет свою собственную перегрузку операторов равенства / неравенства для проверки ссылочного равенства объектов, которые не включают тест на равенство значений. Лично мне не нравится такой языковой дизайн (vb.net использует операторыIs а такжеIsNot проверить равенство ссылок; применительно к типам Framework,= а также<> проверит равенство значений, если они компилируются вообще. Однако ничто не мешает любому типу перегружать эти операторы, чтобы означать что-то совершенно иное.
1

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

  1. Reference Equality, object.ReferenceEquals(a, b): The two variables point to the same exact object in RAM. (If this were C, both variables would have the same exact pointer.)

  2. Interchangeability, a == b: The two variables refer to objects that are completely interchangeable. Thus, when a == b, Func(a,b) and Func(b,a) do the same thing.

  3. Semantic Equality, object.Equals(a, b): At this exact moment in time, the two objects mean the same thing.

  4. Entity equality, a.Id == b.Id: The two objects refer to the same entity, such as a database row, but don’t have to have the same contents.

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

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

1

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

Изменить: связанная статья Кевина Шеффилда лучше объясняет ценность и ссылочное равенство & # x2026;

Согласен, но это звучит как хорошее эмпирическое правило.
Это наоборот. == для конкретного равенства и Равные для концептуального равенства.
Неправильно.ReferenceEquals этоidentity проверить в .Net. ЕслиEquals всегда делал тест на личность, тогда не было бы смысла иметь оба ...
9

== вести себя как можно болееEquals:

DO ensure that Object.Equals and the equality operators have exactly the same semantics

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

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

If a class implementor does not override ==Затем статический метод ищется во время компиляции в базовых классах. Если этот поиск достигаетObject, затемObject.== используется. Для классов это так же, какReferenceEquals.

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

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

Removed controversial commentary regarding ==

UPDATE Недавняя цитата из документа Microsoft .Net 4.7.2, полученная в феврале 2019 года, показывает, что ониstill намерены вести себя одинаково:

Метод Object.Equals

Some languages such as C# and Visual Basic support operator overloading. When a type overloads the equality operator, it must also override the Equals(Object) method to provide the same functionality. This is typically accomplished by writing the Equals(Object) method in terms of the overloaded equality operator, as in the following example.

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

Мой ответ наstackoverflow.com/a/54892972/1711103 есть полные примеры того, когда использовать == против равных.
Я добавил еще один ответ, который может немного изменить ваше мнение (не то, чтобы я возражал против ваших рассуждений). призвание.Equals() для нулевого объекта (сгенерировано исключение) по сравнению с использованием статического оператора, который не требует создания экземпляра любого операнда (без исключения, работает как положено).
Равно это семантическое равенство; два объекта могут быть равны, но имеют очень тонкие различия. == означает, что объекты полностью взаимозаменяемы. Разница очень важна при работе с изменяемыми объектами o, bject, потому что вы можете изменить состояние одного объекта и, следовательно, результат Equals, в любое время, но вы не можете изменить результат ==. С неизменяемыми объектами семантика различна, потому что Equals и == никогда не меняются. Вот почему переопределение == рекомендуется только для неизменяемых объектов. Это различие имеет решающее значение, поскольку при работе с изменяемым объектом другой поток или метод может его изменить.
Отличный ответ. Благодарю. Что касается вашего комментария в другом месте на этой странице о том, что он «озадачен» тем, почему многие из этих ответов ссылаются на эту статью & quot; - это StackOverflow: речь идет не о том, чтобы прочитать полный вопрос и ответить на него, а о том, как быстро вы можете найти что-то, что можно придираться к самому вопросу. Следовательно, почему человек, критикующий вопрос, находится вверху страницы, и ваш ответ, который окончательно проясняет этот беспорядок в .NET, изо всех сил пытается получить голоса. :-)
@AndrewRondeau - задокументированное намерение Microsoft заключается в том, чтобы== оператор ведет себя так же, какEquals; отсюда цитата в моем ответе. OTOH, Microsoft не обеспечивает это. Если вы, как дизайнер класса, решите провести различие, которое вы указали, вас ничто не остановит. Можете ли вы описатьspecific Ситуация, когда вы сочли это желательным? Кроме того, «вы не можете» изменить результат==& Quot ;. Да, ты можешь; определить перегрузку оператора== для вашего класса. Часто один реализует== какEquals; действительно документ Microsoft, я ссылаюсьrecommends делать это.
17

метод object.Equals

оператор ==

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

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

Is this a good thing, what are the differences, and when/why should you use one over the other?

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

@aku: этот ответ будет удобнее, если выsummarized существенная разница (и) между двумя операторами. Это не доfourth link (guidelines for overriding) that microsoft starts discussing the two equalities at the same time - что необходимо для ответа на вопрос. (И я почти не удосужился нажать на 4-ую ссылку, потому что ее название не звучало многообещающе, а 3-я ссылка казалась совершенно неактуальной.)
& Quot; вслепую & Quot; это плохая практика для всего. если вы знаете ответ на свой вопрос, зачем спрашивать?
Когда кто-то попадает в дженерики, различия могут быть огромными, если вы слепо вызываете их для любого типа Т. Matthew Scharley
Почему это принятый ответ? Это ужасный ответ.
Даже если бы я знал конкретный ответ (который я не знаю), возможно, по той же причине, по которой люди задают вопросы и сами отвечают на них? Кроме того, как вы можете сделать что-нибудь еще для универсального типа T? Если вы начинаете делать что-то вроде if (typeof (T) == typeof (int)), какой смысл? Matthew Scharley

Похожие вопросы