Оператор перегрузки == versus Equals()

Я работаю над проектом С#, на котором до сих пор я использовал неизменяемые объекты и фабрики, чтобы гарантировать, что объекты типа Foo всегда можно сравнить для равенства с ==.

Foo объекты не могут быть изменены после создания, а factory всегда возвращает один и тот же объект для заданного набора аргументов. Это отлично работает, и на всей базе кода мы предполагаем, что == всегда работает для проверки равенства.

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

Является ли это законной проблемой, или я должен просто идти вперед и перегружать operator ==?

Ответ 1

Я считаю, что стандартом является то, что для большинства типов .Equals проверяет сходство объектов, а оператор == проверяет ссылочное равенство.

Я считаю, что наилучшая практика заключается в том, что для неизменяемых типов оператор == должен проверять подобие, а также .Equals. И если вы хотите знать, действительно ли они являются одним и тем же объектом, используйте .ReferenceEquals. Для примера см. Класс С# String.

Ответ 2

Существует большая разница между перегрузкой == и переопределением Равнов.

Если у вас есть выражение

if (x == y) {

Метод, который будет использоваться для сравнения переменных x и y, определяется с компиляцией. Это перегрузка оператора. Тип, используемый при объявлении x и y, используется для определения того, какой метод используется для их сравнения. Фактический тип внутри x и y (т.е. Подкласс или реализация интерфейса) не имеет значения. Рассмотрим следующее.

object x = "hello";
object y = 'h' + "ello"; // ensure it a different reference

if (x == y) { // evaluates to FALSE

и следующих

string x = "hello";
string y = 'h' + "ello"; // ensure it a different reference

if (x == y) { // evaluates to TRUE

Это демонстрирует, что тип, используемый для объявления переменных x и y, используется для определения того, какой метод используется для вычисления ==.

Для сравнения, Equals определяется в время выполнения на основе фактического типа внутри переменной x. Equals - это виртуальный метод Object, который другие типы могут и делают. Поэтому следующие два примера оцениваются как истинные.

object x = "hello";
object y = 'h' + "ello"; // ensure it a different reference

if (x.Equals(y)) { // evaluates to TRUE

и следующих

string x = "hello";
string y = 'h' + "ello"; // ensure it a different reference

if (x.Equals(y)) { // also evaluates to TRUE

Ответ 3

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

Смотрите статью Microsoft для некоторых полезных рекомендаций

Ответ 4

Это определенно пахнет. При перегрузке == вы должны убедиться, что оба Equals() и GetHashCode() также согласованы. См. рекомендации MSDN.

И единственная причина, по которой это вообще кажется ОК, заключается в том, что вы описываете свой тип как непреложный.

Ответ 6

Пример, показывающий, как реализовать это в соответствии с рекомендациями MSFT (ниже). Обратите внимание: при переопределении Equals вам также необходимо переопределить GetHashCode(). Надеюсь, это поможет людям.

public class Person
{
    public Guid Id { get; private set; }

    public Person(Guid id)
    {
        Id = id;
    }

    public Person()
    {
        Id = System.Guid.NewGuid();
    }

    public static bool operator ==(Person p1, Person p2)
    {
        bool rc;

        if (System.Object.ReferenceEquals(p1, p2))
        {
            rc = true;
        }
        else if (((object)p1 == null) || ((object)p2 == null))
        {
            rc = false;
        }
        else
        {
            rc = (p1.Id.CompareTo(p2.Id) == 0);
        }

        return rc;
    }

    public static bool operator !=(Person p1, Person p2)
    {
        return !(p1 == p2);
    }

    public override bool Equals(object obj)
    {
        bool rc = false;
        if (obj is Person)
        {
            Person p2 = obj as Person;
            rc = (this == p2);
        }
        return rc;
    }

    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}