如何最好地实现自定义类型的等于?

时间:2009-02-19 22:59:37

标签: c# .net class

说一个Point2类,以及以下等于:

public override bool Equals ( object obj )

public bool Equals ( Point2 obj )

这是有效C#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 ) ) );
}

11 个答案:

答案 0 :(得分:34)

在采用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,以确保“相等”的对象具有相同的哈希码。

答案 1 :(得分:25)

还有一整套guidelines on MSDN。你应该好好读一读,这既棘手又重要。

我发现最有帮助的几点:

  • 价值类型没有标识,因此在 struct Point 中,您通常会按成员比较来执行会员。

  • 引用类型通常具有标识,因此Equals测试通常在ReferenceEquals处停止(默认情况下,无需覆盖)。但是有一些例外,例如字符串和你的 class Point2 ,其中一个对象没有有用的标识,然后你覆盖Equality成员来提供你自己的语义。在这种情况下,请遵循指南首先完成null和其他类型的案例。

  • 并且有充分的理由让GethashCode()operator==保持同步。

答案 2 :(得分:9)

我使用的技术对我有用,如下所示。注意,我只是基于单个属性(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);
        }
    }
}

答案 3 :(得分:2)

  • 定义标识的含义..如果引用标识,那么默认的继承等于将起作用。
  • 如果您需要定义值类型(以及值标识)。
  • 如果是类类型,但具有值语义,则定义。

可能你想两者覆盖Equals(对象)并定义Equals(MyType),因为后者避免装箱。并覆盖相等运算符。

.NET Framework指南(第2版)有更多的报道。

答案 4 :(得分:0)

Lie Daniel L说,

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) {
    ...
}

答案 5 :(得分:0)

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 )

答案 6 :(得分:0)

其他几个人已经发布了一些表格的轻微变体......

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到Type)。

如果我们可以将属性添加到需要比较的字段/属性/方法中,并让编译器/运行时处理所有单调乏味,那就太棒了。

同时确保GetHashCode()值对于.Equals(object)返回true或疯狂的shit可能发生的任何实例都是相等的。

答案 7 :(得分:0)

还有一个自动生成Equals()和GetHashCode()的Fody插件Equals.Fody

答案 8 :(得分:0)

覆盖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方法,以允许类型在哈希表中正常工作。

答案 9 :(得分:0)

使用C#7和is type varname模式(https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/is#type),可以提供干净的Equals(object),它可以使用以下任一方法处理null和类型检查:

// using Equals(point)
public override bool Equals(object obj) =>
    (obj is Point other) && this.Equals(other);

// using the == operator
public override bool Equals(object obj) =>
    (obj is Point other) && this == other;

很显然,您还需要至少实现 以下之一:

public bool Equals(Point2 other);
public static bool operator == (Point2 lhs, Point2 rhs);

答案 10 :(得分:0)

我发现这对我有用 - 我让 GetHashCode() 来完成所有繁重的工作。

之后,任意两个对象相等当且仅当它们的哈希码相等。

    public override int GetHashCode() => <-- IMPLEMENT THIS -->
    public virtual bool Equals(T other) => other?.GetHashCode() == GetHashCode();
    public override bool Equals(object other) => Equals(other as T);
    public static bool operator ==(T item1, T item2) => item1?.GetHashCode() == item2?.GetHashCode();
    public static bool operator !=(T item1, Titem2) => !(item1 == item2);

向 S 类型的密封类添加一些信息的 Wrapper 类示例。

public class Wrapper<S>
{
    public S obj;

    // Handle obj being null
    public override int GetHashCode() => obj?.GetHashCode() ?? 0;

    // The rest are the same as above
    public virtual bool Equals(Wrapper<S> other) => other?.GetHashCode() == GetHashCode();
    public override bool Equals(object other) => Equals(other as Wrapper<S>);
    public static bool operator ==(Wrapper<S> item1, Wrapper<S> item2) => item1?.GetHashCode() == item2?.GetHashCode();
    public static bool operator !=(Wrapper<S> item1, Wrapper<S> item2) => !(item1 == item2);
}