Реализовать Equals () для ссылочных типов сложнее, чем кажется. Моя текущая каноническая реализация выглядит так:
public bool Equals( MyClass obj )
{
// If both refer to the same reference they are equal.
if ( ReferenceEquals( obj, this ) )
return true;
// If the other object is null they are not equal because in C# this cannot be null.
if ( ReferenceEquals( obj, null ) )
return false;
// Compare data to evaluate equality
return _data.Equals( obj._data );
}
public override bool Equals( object obj )
{
// If both refer to the same reference they are equal.
if ( ReferenceEquals( obj, this ) )
return true;
// If the other object is null or is of a different types the objects are not equal.
if ( ReferenceEquals( obj, null ) || obj.GetType() != GetType() )
return false;
// Use type-safe equality comparison
return Equals( (MyClass)obj );
}
public override int GetHashCode()
{
// Use data's hash code as our hashcode
return _data.GetHashCode();
}
Я думаю, что это касается всех угловых случаев (наследования и т. д.), Но я могу ошибаться. Что вы думаете, ребята?





Это зависит от того, пишете ли вы тип значения или ссылочный тип. Для сортируемого типа значения я рекомендую следующее: Фрагмент кода для Visual Studio 2005, который реализует тип каркасного значения в соответствии с рекомендациями по проектированию платформы.
-1, как указывалось, исходный вопрос был специфичен для ссылочных типов, поэтому этот ответ ничего не добавляет.
@cori, если вы сравните написание ссылки Equals со значением Equals, и вы выяснили, что вы написали значение Equals для ссылки, тогда это может быть неправильно. Так что в этом отношении есть некоторая ценность.
Что касается наследования, я думаю, вам следует просто позволить парадигме объектно-ориентированного программирования творить чудеса.
В частности, следует удалить проверку GetType(), это может нарушить полиморфизм в дальнейшем.
Я не согласен. Представьте, что у нас есть классы Apple и Orange, производные от класса Fruit. Если мы удалим проверку GetType () в реализации Equals in Fruit, мы сможем сравнивать яблоки с апельсинами, если каждый производный класс также не переопределяет Equals () должным образом. Это могло очень быстро испортиться.
Я согласен с чакритом, объекты разных типов должны иметь возможность быть семантически равными, если они имеют одинаковые данные или идентификатор.
Лично я использую следующее:
public override bool Equals(object obj)
{
var other = obj as MyClass;
if (other == null) return false;
return this.data.Equals(other.data);
}
Я не согласен с вами. Это позволило бы нам сравнивать яблоки с апельсинами.
Дело в том, что, может быть, вы действительно хотите, чтобы они были равны. Например, проверка GetType не позволяет использовать прокси; у вас будет 2 одинаковых объекта, один из которых заключен в контейнер другого типа, но вы хотите идентифицировать их как одинаковые.
@SantiagoPalladino: часто имеет смысл определять компараторы равенства, которые используют более свободное определение эквивалентности, чем Object.Equals. Например, .net определяет различные нечувствительные к регистру средства сравнения строк, которые будут рассматривать «HELLO» как эквивалент «Hello» или «heL10», даже если строки разные. Такие компараторы могут принимать как одинаковые объекты, так и разные типы. Мне очень не нравится понятие объектов, заменяющих Object.Equals для определения отношения эквивалентности, которое является настолько свободным, что позволяет объектам разных типов сравнивать неравные, если только один явно не предусматривает ...
... возможность того, что вызов X.Equals(Y) может потребовать от X вызова Y.Equals(X), если Y является экземпляром более производного типа, чем X. В противном случае, если прокси знает, как сравнивать себя с объектом, но объект не знает, как сравнивать себя с прокси, метод Equals не будет определять правильное отношение повторения (которое, по определению, должно быть рефлексивным).
Лучше надейтесь, что this._data не равно нулю, если это также ссылочный тип.
public bool Equals( MyClass obj )
{
if (obj == null) {
return false;
}
else {
return (this._data != null && this._data.Equals( obj._data ))
|| obj._data == null;
}
}
public override bool Equals( object obj )
{
if (obj == null || !(obj is MyClass)) {
return false;
}
else {
return this.Equals( (MyClass)obj );
}
}
public override int GetHashCode() {
return this._data == null ? 0 : this._data.GetHashCode();
}
Ты прав. Это просто «каноническая» реализация, чтобы доказать концепции, лежащие в основе «Equals». Моя «настоящая» реализация обычно реализуется с использованием Equals (a, b) вместо a.Equals (b).
Некоторое время назад я написал довольно подробное руководство по этому поводу. Для начала ваши реализации equals должны быть общими (т.е. перегрузка, принимающая объект, должна передаваться той, которая принимает строго типизированный объект). Кроме того, вам нужно учитывать такие вещи, как ваш объект, должен быть неизменным из-за необходимости переопределить GetHashCode. Больше информации здесь:
http://gregbeech.com/blog/implementing-object-equality-in-dotnet
Моя реализация «общая». Как видите, в конце Equals (Object) есть вызов Equals (MyClass). Мне известно о проблемах изменчивости и GetHashCode (); но это важное наблюдение. Он укусил меня несколько раз. Жаль, что нет "простого" способа объявить "классы" только для чтения.
Я знаю, что реализация для типов значений отличается. Я спрашивал о ссылочных типах.