Что такое «лучшая практика» для сравнения двух экземпляров эталонного типа?

Я столкнулся с этим недавно, до сих пор я с радостью переопределял оператор равенства (==) и / или метод Равно, чтобы увидеть, действительно ли два типа ссылок содержат один и тот же данные (т.е. два разных экземпляра, которые выглядят одинаково).

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

Просматривая некоторые из рекомендации по стандартам кодирования в MSDN, я наткнулся на статья, который не рекомендует его. Теперь я понимаю, что Почему в статье говорится об этом (потому что они не совпадают с пример), но не отвечает на вопрос:

  1. Как лучше всего сравнить два ссылочных типа?
  2. Должны ли мы реализовать IComparable? (Я также видел упоминание о том, что это должно быть зарезервировано только для типов значений).
  3. Есть какой-то интерфейс, о котором я не знаю?
  4. Должны ли мы просто катить свои собственные ?!

Большое спасибо ^ _ ^

Обновлять

Похоже, я неправильно прочитал часть документации (это был долгий день), и переопределение Равно может быть выходом ..

If you are implementing reference types, you should consider overriding the Equals method on a reference type if your type looks like a base type such as a Point, String, BigNumber, and so on. Most reference types should not overload the equality operator, even if they override Equals. However, if you are implementing a reference type that is intended to have value semantics, such as a complex number type, you should override the equality operator.

«Большинство ссылочных типов не должны перегружать оператор равенства, даже если они переопределяют Equals»? Вау, я нахожу это немного ... гм ... странным. Таким образом, a.Equals (b) может быть истинным, а a == b может быть ложным. Если я хочу знать, равны ли ссылки (что бывает редко, честно говоря), я бы в любом случае использовал .ReferenceEquals (a, b). Мне нравится, когда a == b возвращает то же самое, что и a.Equals (b). Разве это не «лучшая практика»?

Flipster 19.11.2010 00:30

@FlipScript: Основная проблема с переопределением оператора == заключается в том, что на самом деле это два оператора; когда он используется с типами, для которых существуют переопределения, он использует переопределение; в противном случае, если операнды являются ссылочными типами, это проверка на равенство ссылок. Поскольку == привязан статически, а не виртуально, даже при использовании с универсальными шаблонами такое поведение может привести к неожиданным результатам. В vb.net отдельные операторы используются для переопределения равенства и ссылочного равенства, чтобы избежать такой двусмысленности.

supercat 19.11.2012 07:30
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
47
2
29 494
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Для сложных объектов, которые приводят к конкретным сравнениям, хорошей реализацией является реализация IComparable и определение сравнения в методах Compare.

Например, у нас есть объекты «Транспортное средство», где единственной разницей может быть регистрационный номер, и мы используем его для сравнения, чтобы гарантировать, что ожидаемое значение, возвращаемое при тестировании, является тем, которое мы хотим.

Спасибо за это, Пол. Отмечено в интерфейсе IComparable, хотя я думаю, что в этом случае это, вероятно, будет излишним, поскольку я хочу просто проверить равенство.

Rob Cooper 19.09.2008 22:32
Ответ принят как подходящий

Похоже, вы пишете код на C#, в котором есть метод Equals, который должен реализовать ваш класс, если вы хотите сравнить два объекта с использованием какой-либо другой метрики, отличной от этих двух указателей (потому что дескрипторы объектов - это всего лишь указатели) на тот же адрес памяти? ".

Я взял пример кода из здесь:

class TwoDPoint : System.Object
{
    public readonly int x, y;

    public TwoDPoint(int x, int y)  //constructor
    {
        this.x = x;
        this.y = y;
    }

    public override bool Equals(System.Object obj)
    {
        // If parameter is null return false.
        if (obj == null)
        {
            return false;
        }

        // If parameter cannot be cast to Point return false.
        TwoDPoint p = obj as TwoDPoint;
        if ((System.Object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public bool Equals(TwoDPoint p)
    {
        // If parameter is null return false:
        if ((object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public override int GetHashCode()
    {
        return x ^ y;
    }
}

Java имеет очень похожие механизмы. Метод равно () является частью класса Объект, и ваш класс перегружает его, если вам нужны такие функциональные возможности.

Причина, по которой перегрузка '==' может быть плохой идеей для объектов, заключается в том, что, как правило, вы все еще хотите иметь возможность выполнять сравнения "это один и тот же указатель". На них обычно полагаются, например, для вставки элемента в список, в котором не допускаются дубликаты, и некоторые элементы вашего фреймворка могут не работать, если этот оператор перегружен нестандартным способом.

Хороший ответ, спасибо. Я рад, что вы добавили бит о том, почему нет перегружает оператор равенства.

Rob Cooper 19.09.2008 22:31

На самом деле это одна из слабых сторон C#. Однако пока разработчик следует рекомендациям, это не проблема, потому что семантика == не будет изменена для равных ссылок. Тем не менее, я использую object.ReferenceEquals в критических ситуациях на C# (вместо этого у VB есть Is).

Konrad Rudolph 19.09.2008 22:40

Вы не должны писать логику равенства в двух местах. Не уверен, как MS ошиблась ..

nawfal 17.12.2012 02:25

В этой статье просто рекомендуется не переопределять оператор равенства (для ссылочных типов), а не переопределять Equals. Вы должны переопределить Equals в своем объекте (ссылке или значении), если проверка равенства будет означать нечто большее, чем проверка ссылок. Если вам нужен интерфейс, вы также можете реализовать IEquatable (используется универсальными коллекциями). Однако, если вы реализуете IEquatable, вам также следует переопределить equals, как говорится в разделе примечаний IEquatable:

If you implement IEquatable<T>, you should also override the base class implementations of Object.Equals(Object) and GetHashCode so that their behavior is consistent with that of the IEquatable<T>.Equals method. If you do override Object.Equals(Object), your overridden implementation is also called in calls to the static Equals(System.Object, System.Object) method on your class. This ensures that all invocations of the Equals method return consistent results.

Что касается того, следует ли вам реализовать Equals и / или оператор равенства:

От Реализация метода Equals

Most reference types should not overload the equality operator, even if they override Equals.

От Рекомендации по реализации равенства и оператора равенства (==)

Override the Equals method whenever you implement the equality operator (==), and make them do the same thing.

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

Я предпочитаю использовать то, что Resharper делает автоматически. например, он автоматически создал это для одного из моих ссылочных типов:

public override bool Equals(object obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    return obj.GetType() == typeof(SecurableResourcePermission) && Equals((SecurableResourcePermission)obj);
}

public bool Equals(SecurableResourcePermission obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    return obj.ResourceUid == ResourceUid && Equals(obj.ActionCode, ActionCode) && Equals(obj.AllowDeny, AllowDeny);
}

public override int GetHashCode()
{
    unchecked
    {
        int result = (int)ResourceUid;
        result = (result * 397) ^ (ActionCode != null ? ActionCode.GetHashCode() : 0);
        result = (result * 397) ^ AllowDeny.GetHashCode();
        return result;
    }
}

Если вы хотите переопределить == и по-прежнему выполнять проверки ссылок, вы все равно можете использовать Object.ReferenceEquals.

Как сделать так, чтобы ReSharper делал все это автоматически?

Svish 12.03.2009 16:50

Реализовать равенство в .NET правильно, эффективно и без дублирования кода сложно. В частности, для ссылочных типов с семантикой значений (например, неизменяемые типы, которые рассматривают равноправие как равенство) вы должны реализовать интерфейс System.IEquatable<T>, и вы должны реализовать все различные операции (Equals, GetHashCode и ==, !=).

В качестве примера приведем класс, реализующий равенство значений:

class Point : IEquatable<Point> {
    public int X { get; }
    public int Y { get; }

    public Point(int x = 0, int y = 0) { X = x; Y = y; }

    public bool Equals(Point other) {
        if (other is null) return false;
        return X.Equals(other.X) && Y.Equals(other.Y);
    }

    public override bool Equals(object obj) => Equals(obj as Point);

    public static bool operator ==(Point lhs, Point rhs) => object.Equals(lhs, rhs);

    public static bool operator !=(Point lhs, Point rhs) => ! (lhs == rhs);

    public override int GetHashCode() => X.GetHashCode() ^ Y.GetHashCode();
}

Единственными подвижными частями в приведенном выше коде являются выделенные жирным шрифтом части: вторая строка в Equals(Point other) и метод GetHashCode(). Другой код должен остаться без изменений.

Для ссылочных классов, которые не представляют неизменяемые значения, не реализуйте операторы == и !=. Вместо этого используйте их значение по умолчанию, которое заключается в сравнении идентичности объекта.

Код намеренно приравнивает четные объекты типа производного класса. Часто это может быть нежелательно, потому что равенство между базовым классом и производными классами четко не определено. К сожалению, .NET и правила кодирования здесь не очень ясны. Код, созданный Resharper и отправленный в другом ответе, подвержен нежелательному поведению в таких случаях, потому что Equals(object x) и Equals(SecurableResourcePermission x)буду обрабатывают этот случай по-разному.

Чтобы изменить это поведение, необходимо добавить дополнительную проверку типа в строго типизированный метод Equals, описанный выше:

public bool Equals(Point other) {
    if (other is null) return false;
    if (other.GetType() != GetType()) return false;
    return X.Equals(other.X) && Y.Equals(other.Y);
}

Хороший ответ, Конрад, на самом деле слишком хорош для этого вопроса (т.е. мне действительно нужна только сторона равенства)! Хороший ответ, +1 от меня. :)

Rob Cooper 19.09.2008 22:34

Для классов, почему вы переопределяете операторы равенства и неравенства для выполнения сравнения ссылок, если эта функциональность предоставляется по умолчанию базовым классом System.Object?

Zach Burlingame 08.04.2009 21:04

@Burly: Я не знаю. Они должны выполнить сравнение ценить (обратите внимание на lhs.Equals(rhs) в конце)! Проверки ReferenceEquals просто экономят время (при определенных обстоятельствах), потому что, если две ссылки равны, нам также не нужно проверять равенство значений (x == x, всегда).

Konrad Rudolph 08.04.2009 21:34

Ах, я пропустил lhs.Equals (rhs) в конце. Считается лучшей практикой / не / реализовывать равенство значений в операторах == и! = Для ссылочных типов, которые не являются неизменяемыми. Однако вы должны переопределить их для типов значений. См. Мой ответ ниже, чтобы узнать об источнике и его обсуждении.

Zach Burlingame 09.04.2009 00:18

Считается лучшей практикой всегда заставлять Equals и == выполнять одинаковые действия. Это отражено в моем фрагменте кода. Очевидно, используйте его только в том случае, если такая семантика имеет смысл. Но всегда заставляйте Equals и == работать одинаково. Если они этого не сделают, это абсолютный ужас юзабилити.

Konrad Rudolph 09.04.2009 12:01

И да, мне известно, что вы заявили кое-что еще: я категорически не согласен с этим утверждением.

Konrad Rudolph 09.04.2009 12:05

Как вы думаете, почему Equals и == должны быть согласованы? Это противоречит тому, что указано в документации MSDN, а также создает аналогичное отключение, где == больше не означает ссылочное равенство. Это создает аналогичную проблему удобства использования, поскольку такое поведение всегда обеспечивается .NET.

Zach Burlingame 10.04.2009 19:38

FWIW, я, конечно, понимаю, откуда вы, особенно из мира C++. Однако, поскольку документация / рекомендации MSDN явно не рекомендуют то, что вы делаете, я ищу веский аргумент в пользу вашей позиции. Возможно, это заслуживает отдельного вопроса ..

Zach Burlingame 10.04.2009 19:41

+ за сниппет, отлично! Небольшое предложение: не могли бы вы переместить object.ReferenceEquals(lhs, rhs) с == на общий Equals? Итак, это дает скорость (конечно, это зависит, но в худшем случае) проверки эталонного равенства даже для общего метода Equals?

nawfal 17.12.2012 01:44

@nawfal Нет, он нам нужен в ==, иначе мы получим неверный результат для ((Point) null) == ((Point) null); а именно, он вернет false, что является ошибкой. Конечно, мы могли бы также выполнить проверку в Equals. Фактически, в тех случаях, когда это имеет смысл, это абсолютно необходимо сделать.

Konrad Rudolph 17.12.2012 03:33

@KonradRudolph Я знал, что удаление этого кода как такового приведет к ошибкам, но я думаю, что мы можем немного подправить это, чтобы не было ошибок. Я сделал нечто подобное в своем ответе. Я думаю, что так лучше (в этом отношении).

nawfal 17.12.2012 03:43

@nawfal Да, к сожалению, мой хост отключился несколько месяцев назад, и пока мне не удалось связаться с кем-либо, чтобы это исправить. Я все еще надеюсь, что они когда-нибудь отреагируют.

Konrad Rudolph 02.10.2013 16:52

@KonradRudolph грустно, ты не можешь загрузить его где-нибудь еще?

nawfal 02.10.2013 17:41

@nawfal У меня больше нет кода, мне также понадобится доступ к моему веб-пространству… :(

Konrad Rudolph 02.10.2013 18:22

не должен Equals проверять ReferenceEquals (this, other)? в случае, если кто-то делает Equals (это)? (вместо in ==)

kofifus 02.07.2018 09:19

@kofifus Можно, но это излишне и, вероятно, неэффективно (ожидается, что проверки самодостаточности будут редкими и также займут время)

Konrad Rudolph 02.07.2018 11:08

спасибо .. также визуальная студия предлагает заменить (object.ReferenceEquals (other, null)) на (other is null) - это нормально?

kofifus 03.07.2018 00:19

@kofifus Вы используете VB? Тогда да, вы можете / должны заменить использование всеobject.ReferenceEquals оператором Is. В C# вы не можете этого сделать, потому что сопоставление с образцом is вызывает Object.Equals под капотом, который, в свою очередь, вызывает метод Equals, который мы переопределяем. Таким образом, вы получите бесконечную рекурсию. Если Visual Studio дает вам этот совет на C#, то, к сожалению, этот совет неверен.

Konrad Rudolph 03.07.2018 13:12

да, визуальная студия 2017 C#

kofifus 03.07.2018 13:22

@kofifus На самом деле ничего: я тестировал, other is null работает. Очевидно, статический Object.Equals выполняет свою собственную (строго говоря, избыточную!) Проверку null для второго аргумента и, следовательно, не вызывает наш переопределенный метод Equals (также его документацию). Так что да, замените его, как предлагает VS. Но учтите, что это будет работать только в C# 7.0 или новее. Предыдущие версии C# не поддерживали такое использование is.

Konrad Rudolph 03.07.2018 13:25

хорошо, отлично и спасибо! Я разместил версию вашего ответа выше

kofifus 03.07.2018 14:34

Я думаю, что есть крайний случай, который не рассматривается: предположим классы Base и Derived и предположим (new Derived (1)). Equals ((new Derived (1))) == true, затем Base mybase1 = new Derived (1); База mybase2 = новый производный (1); mybase1.Equals (mybase2) должен возвращать true

kofifus 04.07.2018 04:28

@kofifus Этот случай тоже закрыт. Наряду с любой другой комбинацией базового / производного сравнения. Более того, это работает правильно (а), если только Base отменяет сравнение на равенство и реализует IEquatable<Base>, а также (b), если Derived также отменяет сравнение и реализует IEquatable<Derived>.

Konrad Rudolph 04.07.2018 12:36

Я вижу ваше изменение, почему вы удалили тест ReferenceEquals?

kofifus 04.07.2018 14:02

Позвольте нам продолжить обсуждение в чате.

Konrad Rudolph 04.07.2018 16:04

Ниже я суммировал, что вам нужно сделать при реализации IEquatable, и представил обоснование на различных страницах документации MSDN.


Резюме

  • Если требуется проверка равенства значений (например, при использовании объектов в коллекциях), вы должны реализовать интерфейс IEquatable, переопределить Object.Equals и GetHashCode для своего класса.
  • Если требуется проверка эталонного равенства, вы должны использовать operator ==, operator! = И Object.ReferenceEquals.
  • Вы должны переопределять operator == и operator! = Только для ValueTypes и неизменяемых ссылочных типов.

Обоснование

IEquatable

The System.IEquatable interface is used to compare two instances of an object for equality. The objects are compared based on the logic implemented in the class. The comparison results in a boolean value indicating if the objects are different. This is in contrast to the System.IComparable interface, which return an integer indicating how the object values are different.

The IEquatable interface declares two methods that must be overridden. The Equals method contains the implementation to perform the actual comparison and return true if the object values are equal, or false if they are not. The GetHashCode method should return a unique hash value that may be used to uniquely identify identical objects that contain different values. The type of hashing algorithm used is implementation-specific.

IEquatable.Equals Метод

  • You should implement IEquatable for your objects to handle the possibility that they will be stored in an array or generic collection.
  • If you implement IEquatable you should also override the base class implementations of Object.Equals(Object) and GetHashCode so that their behavior is consistent with that of the IEquatable.Equals method

Рекомендации по переопределению Equals () и Operator == (Руководство по программированию на C#)

  • x.Equals(x) returns true.
  • x.Equals(y) returns the same value as y.Equals(x)
  • if (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true.
  • Successive invocations of x. Equals (y) return the same value as long as the objects referenced by x and y are not modified.
  • x. Equals (null) returns false (for non-nullable value types only. For more information, see Nullable Types (C# Programming Guide).)
  • The new implementation of Equals should not throw exceptions.
  • It is recommended that any class that overrides Equals also override Object.GetHashCode.
  • Is is recommended that in addition to implementing Equals(object), any class also implement Equals(type) for their own type, to enhance performance.

By default, the operator == tests for reference equality by determining whether two references indicate the same object. Therefore, reference types do not have to implement operator == in order to gain this functionality. When a type is immutable, that is, the data that is contained in the instance cannot be changed, overloading operator == to compare value equality instead of reference equality can be useful because, as immutable objects, they can be considered the same as long as they have the same value. It is not a good idea to override operator == in non-immutable types.

  • Overloaded operator == implementations should not throw exceptions.
  • Any type that overloads operator == should also overload operator !=.

== Оператор (Справочник по C#)

  • For predefined value types, the equality operator (==) returns true if the values of its operands are equal, false otherwise.
  • For reference types other than string, == returns true if its two operands refer to the same object.
  • For the string type, == compares the values of the strings.
  • When testing for null using == comparisons within your operator== overrides, make sure you use the base object class operator. If you don't, infinite recursion will occur resulting in a stackoverflow.

Метод Object.Equals (Object)

If your programming language supports operator overloading and if you choose to overload the equality operator for a given type, that type must override the Equals method. Such implementations of the Equals method must return the same results as the equality operator

The following guidelines are for implementing a value type:

  • Consider overriding Equals to gain increased performance over that provided by the default implementation of Equals on ValueType.
  • If you override Equals and the language supports operator overloading, you must overload the equality operator for your value type.

The following guidelines are for implementing a reference type:

  • Consider overriding Equals on a reference type if the semantics of the type are based on the fact that the type represents some value(s).
  • Most reference types must not overload the equality operator, even if they override Equals. However, if you are implementing a reference type that is intended to have value semantics, such as a complex number type, you must override the equality operator.

Дополнительные ошибки

Использование одного и того же имени для Equals(Object) и Equals(OwnType), возможно, неудачно, поскольку во многих случаях из-за неявных приведений типов ни Equals(OwnType), ни оператор == не могут определить отношение эквивалентности. Если бы я спроектировал .net, метод Object был бы назван EquivalentTo, и можно было бы ожидать, что переопределения будут использовать более строгие стандарты эквивалентности. Например, я бы указал, что 1.0m.EquivalentTo(1.00m) должен быть ложным, но 1.0m.Equals(1.00m) и 1.0m == 1.00m должны быть истинными, поскольку значения равны численно, даже если они не эквивалент.

supercat 04.04.2013 19:35

Я считаю, что получить что-то столь же простое, как проверка объектов на правильность равенства, немного сложно с дизайном .NET.

Для Struct

1) Реализуйте IEquatable<T>. Это заметно улучшает производительность.

2) Поскольку теперь у вас есть собственный Equals, переопределите GetHashCode, а для согласования с различными проверками равенства также переопределите object.Equals.

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

public struct Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        if (obj == null || !(obj is Entity))
            return false;

        return Equals((Entity)obj);
    }

    public static bool operator ==(Entity e1, Entity e2)
    {
        return e1.Equals(e2);
    }

    public static bool operator !=(Entity e1, Entity e2)
    {
        return !(e1 == e2);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

Для класса

Из MS:

Most reference types should not overload the equality operator, even if they override Equals.

Для меня == выглядит как равенство значений, больше как синтаксический сахар для метода Equals. Написание a == b намного более интуитивно понятно, чем написание a.Equals(b). В редких случаях нам нужно проверять ссылочное равенство. На абстрактных уровнях, имеющих дело с логическими представлениями физических объектов, это не то, что нам нужно проверять. Я думаю, что различная семантика для == и Equals может сбивать с толку. Я считаю, что это должен был быть == для равенства ценностей и Equals для справки (или более подходящее название, например, IsSameAs) равенства в первую очередь. Я бы не хотел серьезно относиться к рекомендациям MS здесь не только потому, что это неестественно для меня, но и потому, что перегрузка == не причиняет большого вреда. В этом отличие от отмены необщего Equals или GetHashCode, которые могут укусить, потому что фреймворк нигде не использует ==, а только если мы сами. Единственная реальная выгода, которую я получу от не перегружает == и !=, - это согласованность с дизайном всего фреймворка, над которым я не могу повлиять. И это действительно большое дело, так грустно я буду придерживаться этого.

Со ссылочной семантикой (изменяемые объекты)

1) Замените Equals и GetHashCode.

2) Внедрение IEquatable<T> не является обязательным, но было бы неплохо, если бы он у вас был.

public class Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (ReferenceEquals(null, other))
            return false;

        //if your below implementation will involve objects of derived classes, then do a 
        //GetType == other.GetType comparison
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Entity);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

С семантикой значений (неизменяемые объекты)

Это сложная часть. Можно легко спутать, если не позаботиться.

1) Замените Equals и GetHashCode.

2) Перегрузите == и !=, чтобы они соответствовали Equals. Убедитесь, что он работает для нулей.

2) Внедрение IEquatable<T> не является обязательным, но было бы неплохо, если бы он у вас был.

public class Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (ReferenceEquals(null, other))
            return false;

        //if your below implementation will involve objects of derived classes, then do a 
        //GetType == other.GetType comparison
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Entity);
    }

    public static bool operator ==(Entity e1, Entity e2)
    {
        if (ReferenceEquals(e1, null))
            return ReferenceEquals(e2, null);

        return e1.Equals(e2);
    }

    public static bool operator !=(Entity e1, Entity e2)
    {
        return !(e1 == e2);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

Будьте особенно внимательны, чтобы увидеть, как должно быть, если ваш класс может быть унаследован, в таких случаях вам нужно будет определить, может ли объект базового класса быть равен объекту производного класса. В идеале, если для проверки равенства не используются объекты производного класса, тогда экземпляр базового класса может быть равен экземпляру производного класса, и в таких случаях нет необходимости проверять равенство Type в универсальном Equals базового класса.

В общем, старайтесь не дублировать код. Я мог бы сделать общий абстрактный базовый класс (IEqualizable<T> или около того) в качестве шаблона, чтобы упростить повторное использование, но, к сожалению, в C# это мешает мне наследовать дополнительные классы.

Проблема основной с переопределением оператора == для ссылочных типов (из-за того, что IMHO является дефектом в дизайне C#) заключается в том, что в C# фактически есть два разных оператора, и решение о том, какой оператор использовать, принимается статически во время компиляции . С типами значений можно перегрузить ==, чтобы он проверял равенство значений во всех случаях компилятор примет [4==4.0m и 4==4.0 компилируются и возвращают значение true, но 4.0m==4.0 не компилируется]. Это невозможно со ссылочными типами; учитывая var s1 = "1"; var s2=1.ToString(); Object o1 = s1;, s1 == s2 и o1 == s1, но o1! = s2.

supercat 04.01.2013 00:26

Microsoft, похоже, изменила свою настройку, или, по крайней мере, есть противоречивая информация о том, что оператор равенства не перегружен. Согласно этому Статья Microsoft под названием Как: Определить равенство значений для типа:

«Операторы == и! = Могут использоваться с классами, даже если класс не перегружает их. Однако по умолчанию выполняется проверка равенства ссылок. В классе, если вы перегружаете метод Equals, вы должны перегрузить == и! =, но это не обязательно. "

Согласно Эрику Липперту в его отвечать на вопрос, который я задал о Минимальный код для равенства в C#, он говорит:

"Опасность, с которой вы столкнетесь здесь, заключается в том, что вы получите определенный для вас оператор ==, который по умолчанию ссылается на равенство. Вы легко можете оказаться в ситуации, когда перегруженный метод Equals оценивает равенство, а == ссылается на равенство, а затем вы случайно используете равенство ссылок на вещах, не равных ссылкам, которые имеют равные значения.Это подверженная ошибкам практика, которую трудно обнаружить при проверке человеческого кода.

Пару лет назад я работал над алгоритмом статического анализа для статистического обнаружения этой ситуации, и мы обнаружили, что частота дефектов составляет около двух экземпляров на миллион строк кода во всех изученных нами кодовых базах. Если рассматривать только кодовые базы, которые где-то переопределяли Equals, уровень дефектов был значительно выше!

Более того, рассмотрите затраты и риски. Если у вас уже есть реализации IComparable, то написание всех операторов является тривиальным однострочником, который не будет содержать ошибок и никогда не будет изменен. Это самый дешевый код, который вы когда-либо собирались писать. Если бы у меня был выбор между фиксированной стоимостью написания и тестированием дюжины крошечных методов и неограниченной стоимостью поиска и исправления трудно заметной ошибки, когда вместо равенства значений используется эталонное равенство, я знаю, какой из них я бы выбрал ».

.NET Framework никогда не будет использовать == или! = С любым типом, который вы пишете. Но опасность в том, что случится, если это сделает кто-то другой. Итак, если класс предназначен для стороннего производителя, я всегда предоставляю операторы == и! =. Если класс предназначен только для внутреннего использования группой, я все равно, вероятно, реализовал бы операторы == и! =.

Я бы реализовал операторы <, <=,> и> = только в том случае, если был реализован IComparable. IComparable следует реализовывать только в том случае, если тип должен поддерживать упорядочение - например, при сортировке или использовании в упорядоченном универсальном контейнере, таком как SortedSet.

Если бы у группы или компании была действующая политика никогда не реализовывать операторы == и! = - тогда я бы, конечно, следовал этой политике. Если бы такая политика существовала, было бы разумно применить ее с помощью инструмента анализа кода Q / A, который помечает любое вхождение операторов == и! = При использовании со ссылочным типом.

Все приведенные выше ответы не рассматривают полиморфизм, часто вы хотите, чтобы производные ссылки использовали производные Equals даже при сравнении через базовую ссылку. Пожалуйста, смотрите вопрос / обсуждение / ответы здесь - Равенство и полиморфизм

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