Что не так с определением operator == , но не определением Equals () или GetHashCode ()?

для кода ниже

public struct Person
{
    public int ID;
    public static bool operator ==(Person a, Person b) { return  a.Equals(b); }
    public static bool operator !=(Person a, Person b) { return !a.Equals(b); }
}

почему компилятор дает мне эти предупреждения?
Что плохого в том, чтобы не определять методы ниже?

warning CS0660: 'Person' defines operator == or operator != but
    does not override Object.Equals(object o)

warning CS0661: 'Person' defines operator == or operator != but
    does not override Object.GetHashCode()

8 ответов


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


в общем, переопределение одного, но не всех, сбивает с толку. Пользователь ожидает, что ни один из них не будет переопределен или оба будут иметь одинаковую семантику.

Microsoft рекомендации для этого состояния (среди прочего вещи):

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

  • переопределите метод Equals всякий раз, когда вы реализуете оператор равенства ( = = ), и заставьте их делать то же самое.

в вашем случае, у вас есть веская причина подчиняться Equals (компилятор не реализует автоматически ==) и переопределить только эти два (==/!=). Тем не менее, по-прежнему существует проблема производительности, так как ValueType.Equals использует отражение:

"переопределите метод Equals для определенного типа, чтобы улучшить представление метода и более близко представляют концепцию равенство для типа."

таким образом, по-прежнему рекомендуется переопределить все (==/!=/Equals) в конце концов. Конечно, производительность может не иметь значения для этого тривиального структура.


Я предполагаю, что вы получаете это предупреждение, потому что компилятор не знает, что вы используете Equals на == метод

Предположим, у вас есть эта реализация

public struct  Person
{
    public int ID;
    public static bool operator ==(Person a, Person b) { return Math.Abs(a.ID - b.ID) <= 5; }
    public static bool operator !=(Person a, Person b) { return Math.Abs(a.ID - b.ID) > 5; }
}

затем

 Person p1 = new Person() { ID = 1 };
 Person p2 = new Person() { ID = 4 };

 bool b1 = p1 == p2;
 bool b2 = p1.Equals(p2);

b1 будет правда, но b2 false

-- EDIT--

теперь предположим, что вы хотите сделать это

Dictionary<Person, Person> dict = new Dictionary<Person, Person>();
dict.Add(p1, p1);
var x1 = dict[p2]; //Since p2 is supposed to be equal to p1 (according to `==`), this should return p1

но это было исключение, что-то вроде KeyNotFound

но если добавить

public override bool Equals(object obj)
{
    return Math.Abs(ID - ((Person)obj).ID) <= 5; 
}
public override int GetHashCode()
{
    return 0;
}

вы получите то, что вы хотите.

компилятор просто предупреждает Вас, что вы можете столкнуться с аналогичными условиями


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

  • если есть допустимая операция == между a и b, он должен дать тот же результат, что и a.Equals(b)
  • аналогично, если есть допустимая операция != между a и b, он должен дать тот же результат, что и !a.Equals(b)
  • если два объекта a и , по которому a == b, потом a и b должен производить тот же ключ при хранении в хэш-таблице.

первые два, IMO, очевидны; если вы определяете, что означает, что два объекта равны, вы должны включить все способы проверки равенства двух объектов. Обратите внимание, что компилятор этого не делает (в общем случае,не может) убедитесь, что вы действительно следуете этим правилам. Он не будет выполнять сложный анализ кода тела ваших операторов, чтобы увидеть, если они уже имитируют Equals потому что в худшем случае это может быть эквивалентно решение проблемы остановки.

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

если вы реализовали Equals быть чем-то отличающимся от == компилятор не будет жалуйтесь; вы бы достигли предела того, как сильно C# готов попытаться помешать вам сделать что-то глупое. Он был готов остановить вас от случайного введения тонких ошибок в ваш код, но это позволит вам нарочно сделайте так, если это то, что вы хотите.

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

if (a == b)
{
    var tbl = new HashTable();
    tbl.Add(a, "Test");

    var s = tbl[b];
    Debug.Assert(s.Equals("Test"));
}

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


все, что вам нужно сделать, это добавить еще один член в структуру say Forename.

Итак, если у вас есть два человека с идентификатором 63, но разными именами, они равны или нет?

все зависит от того, какое определение "того же" вы хотите реализовать.

используйте лучший пример структуры, напишите noddy applictaion для выполнения различных методов и посмотрите, что произойдет, когда вы измените определения равенства и / или эквивалентности, если они не все в шаге, вы в конечном итоге с такими вещами !(a = = b)!= (a != b), что может быть правдой, но если вы не переопределите все методы, кто использует ваш код, будет интересно, каковы ваши намерения.

в основном компилятор говорит вам быть хорошим гражданином и сделать ваши намерения ясными.


наверное, потому, что по умолчанию Equals() метод не должен быть достаточно хорош для реальной системы (например, в вашем классе он должен сравнивать


прочитайте страницы MSDN.

CS0660

CS0661

компилятор в основном говорит: "Поскольку вы говорите, что знаете, как сравнить ваш объект, вы должны заставить его сравнивать все время."


Если вы переопределяете Equals и GetHashCode вам даже не нужно будет переопределять операторы, и это более чистый подход. Редактировать: он должен работать так как это структура.


public struct Coord
{
    public int x;
    public int y;

    public Coord(int x, int y)
    {
        this.x = x;
        this.y = y;
    }

    public static bool operator ==(Coord c1, Coord c2)
    {
        return c1.x == c2.x && c1.y == c2.y;
    }

    public static bool operator !=(Coord c1, Coord c2)
    {
        return !(c1 == c2);
    }

    public bool Equals(Coord other)
    {
        return x == other.x && y == other.y;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        return obj is Coord && Equals((Coord) obj);
    }

    public override int GetHashCode()
    {
        return 0;
    }
}

вот пример. Надеюсь, это поможет.