Как наилучшим образом реализовать Equals для пользовательских типов?
Скажите для класса Point2 и следующие значения:
public override bool Equals ( object obj )
public bool Equals ( Point2 obj )
Это тот, который показан в Эффективном С# 3:
public override bool Equals ( object obj )
{
// STEP 1: Check for null
if ( obj == null )
{
return false;
}
// STEP 3: equivalent data types
if ( this.GetType ( ) != obj.GetType ( ) )
{
return false;
}
return Equals ( ( Point2 ) obj );
}
public bool Equals ( Point2 obj )
{
// STEP 1: Check for null if nullable (e.g., a reference type)
if ( obj == null )
{
return false;
}
// STEP 2: Check for ReferenceEquals if this is a reference type
if ( ReferenceEquals ( this, obj ) )
{
return true;
}
// STEP 4: Possibly check for equivalent hash codes
if ( this.GetHashCode ( ) != obj.GetHashCode ( ) )
{
return false;
}
// STEP 5: Check base.Equals if base overrides Equals()
System.Diagnostics.Debug.Assert (
base.GetType ( ) != typeof ( object ) );
if ( !base.Equals ( obj ) )
{
return false;
}
// STEP 6: Compare identifying fields for equality.
return ( ( this.X.Equals ( obj.X ) ) && ( this.Y.Equals ( obj.Y ) ) );
}
Ответы
Ответ 1
Существует целый набор рекомендаций на MSDN. Вы должны хорошо их прочесть, это сложно и важно.
Несколько моментов, которые я нашел наиболее полезными:
-
Типы значений не имеют Identity, поэтому в struct Point
вы, как правило, выполняете элемент с помощью сравнения элементов.
-
У ссылочных типов обычно есть идентификатор, поэтому тест Equals обычно останавливается на ReferenceEquals (по умолчанию, нет необходимости переопределять). Но есть исключения, такие как строка и ваш class Point2
, где объект не имеет полезного идентификатора, а затем вы переопределяете члены Equality для предоставления своей собственной семантики. В этой ситуации следуйте инструкциям, чтобы сначала просмотреть нулевые и другие типы.
-
И есть веские причины для сохранения GethashCode()
и operator==
в синхронизации.
Ответ 2
В том, который принимает obj, если тип obj является Point2, вызовите тип Equals. Внутри определенного типа Equals убедитесь, что все члены имеют одинаковое значение.
public override bool Equals ( object obj )
{
return Equals(obj as Point2);
}
public bool Equals ( Point2 obj )
{
return obj != null && obj.X == this.X && obj.Y == this.Y ...
// Or whatever you think qualifies as the objects being equal.
}
Вероятно, вы также должны переопределить GetHashCode, чтобы убедиться, что объекты, "равные", имеют одинаковый хеш-код.
Ответ 3
Метод, который я использовал, который работал у меня, выглядит следующим образом. Заметьте, я сравниваю только одно свойство (Id), а не два значения. При необходимости отрегулируйте
using System;
namespace MyNameSpace
{
public class DomainEntity
{
public virtual int Id { get; set; }
public override bool Equals(object other)
{
return Equals(other as DomainEntity);
}
public virtual bool Equals(DomainEntity other)
{
if (other == null) { return false; }
if (object.ReferenceEquals(this, other)) { return true; }
return this.Id == other.Id;
}
public override int GetHashCode()
{
return this.Id;
}
public static bool operator ==(DomainEntity item1, DomainEntity item2)
{
if (object.ReferenceEquals(item1, item2)) { return true; }
if ((object)item1 == null || (object)item2 == null) { return false; }
return item1.Id == item2.Id;
}
public static bool operator !=(DomainEntity item1, DomainEntity item2)
{
return !(item1 == item2);
}
}
}
Ответ 4
- Определите, что означает идентификатор. Если ссылочная идентификация будет равна унаследованным равным по умолчанию, будет работать.
- Если тип значения (и, следовательно, значение идентификатора) необходимо определить.
- Если тип класса, но имеет семантику значения, определите.
Вероятно, вы хотите как переопределить Equals (объект), так и определить Equals (MyType), потому что последний избегает бокса. И переопределить оператор равенства.
В книге рекомендаций .NET Framework (2-е изд.) больше внимания.
Ответ 5
Ли Дэниел Л сказал:
public override bool Equals(object obj) {
Point2 point = obj as Point2; // Point2? if Point2 is a struct
return point != null && this.Equals(point);
}
public bool Equals(Point2 point) {
...
}
Ответ 6
public override bool Equals ( object obj )
{
// struct
return obj is Point2 && Equals ( ( Point2 ) value );
// class
//return Equals ( obj as Point2 );
}
public bool Equals ( Point2 obj )
Ответ 7
Небольшие варианты форм, уже отправленные несколькими другими...
using System;
...
public override bool Equals ( object obj ) {
return Equals(obj as SomeClass);
}
public bool Equals ( SomeClass someInstance ) {
return Object.ReferenceEquals( this, someInstance )
|| ( !Object.ReferenceEquals( someInstance, null )
&& this.Value == someInstance.Value );
}
public static bool operator ==( SomeClass lhs, SomeClass rhs ) {
if( Object.ReferenceEquals( lhs, null ) ) {
return Object.ReferenceEquals( rhs, null );
}
return lhs.Equals( rhs );
//OR
return Object.ReferenceEquals( lhs, rhs )
|| ( !Object.ReferenceEquals( lhs, null )
&& !Object.ReferenceEquals( rhs, null )
&& lhs.Value == rhs.Value );
}
public static bool operator !=( SomeClass lhs, SomeClass rhs ) {
return !( lhs == rhs );
// OR
return ( Object.ReferenceEquals( lhs, null ) || !lhs.Equals( rhs ) )
&& !Object.ReferenceEquals( lhs, rhs );
}
Попытка найти способ реализовать operator == с помощью Equals, чтобы избежать дублирования логики сравнения значений... без каких-либо избыточных тестов (вызовы ReferenceEquals с теми же параметрами) или ненужных тестов (это не может быть нулевым в instance.Equals) и без каких-либо явных условий ( "ifs" ). Более разумный тизер, чем что-либо полезное.
Ближе всего я могу думать об этом, но похоже, что это возможно без дополнительного метода:)
public bool Equals ( SomeClass someInstance ) {
return Object.ReferenceEquals( this, someInstance )
|| (!Object.ReferenceEquals( someInstance, null ) && EqualsNonNullInstance( someInstance );
}
public static bool operator ==( SomeClass lhs, SomeClass rhs ) {
return Object.ReferenceEquals( lhs, rhs )
|| ( !Object.ReferenceEquals( lhs, null ) && !Object.ReferenceEquals( rhs, null ) && lhs.EqualsNonNullInstance( rhs ) );
}
//super fragile method which returns logical non-sense
protected virtual bool EqualsNonNullInstance ( SomeClass someInstance ) {
//In practice this would be a more complex method...
return this.Value == someInstance.Value;
}
Помня о том, насколько утомительным и подверженным ошибкам все это (я почти уверен, что в приведенном выше коде есть ошибка... которая все еще сосет, потому что кто хочет подклассифицировать Type только для того, чтобы сделать проверки равенства немного проще?), в будущем Я думаю, что я просто создам некоторые статические методы, которые обрабатывают все нулевые проверки и принимают делегата или требуют и интерфейс для выполнения сравнения значений (единственная часть, которая действительно изменяет Type to Type).
Было бы здорово, если бы мы просто добавили атрибуты в поля/свойства/методы, которые нужно сравнить, и пусть компилятор/среда выполнения будут обрабатывать всю скуку.
Также убедитесь, что значения GetHashCode() равны для всех экземпляров, в которых может быть возвращено истинное или сумасшедшее дерьмо .Equals(object).
Ответ 8
Существует также плагин Fody Equals.Fody, который автоматически генерирует Equals() и GetHashCode()
Ответ 9
Простой и лучший способ переопределить Equals выглядит следующим образом:
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
public override bool Equals(object other)
{
Person otherItem = other as Person;
if (otherItem == null)
return false;
return Age == otherItem.Age && Name == otherItem.Name;
}
public override int GetHashCode()
{
int hash = 13;
hash = (hash * 7) + Age.GetHashCode();
hash = (hash * 7) + Name.GetHashCode();
return hash;
}
}
Переопределите метод GetHashCode, чтобы тип работал правильно в хэш-таблице.