C# 如何最好地为自定义类型实现 Equals?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/567642/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 08:30:30  来源:igfitidea点击:

How to best implement Equals for custom types?

c#.netclass

提问by Joan Venge

Say for a Point2 class, and the following Equals:

说一个 Point2 类,以下等于:

public override bool Equals ( object obj )

public bool Equals ( Point2 obj )

This is the one that is shown in the Effective C# 3:

这是 Effective 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 ) ) );
}

采纳答案by Henk Holterman

There is a whole set of guidelines on MSDNas well. You should read them well, it is both tricky and important.

MSDN 上也有一整套指南。你应该好好阅读它们,它既棘手又重要。

A few points I found most helpful:

我认为最有帮助的几点:

  • Value Types don't have Identity, so in a struct Pointyou will usually do a member by member compare.

  • Reference Types usually do have identity, and therefore the Equals test usually stops at ReferenceEquals (the default, no need to override). But there are exceptions, like string and your class Point2, where an object has no useful identity and then you override the Equality members to provide your own semantics. In that situation, follow the guidelines to get through the null and other-type cases first.

  • And there are good reasons to keep GethashCode()and operator==in sync as well.

  • 值类型没有标识,因此在 a 中,struct Point您通常会逐个成员进行比较。

  • 引用类型通常有标识,因此 Equals 测试通常在 ReferenceEquals 处停止(默认值,无需覆盖)。但也有例外,例如 string 和 your class Point2,其中对象没有有用的标识,然后您覆盖 Equality 成员以提供您自己的语义。在这种情况下,请按照指南首先处理 null 和其他类型的情况。

  • 而且有充分的理由保持GethashCode()operator==同步为好。

回答by Daniel LeCheminant

In the one that takes an obj, if the type of obj is Point2, call the type specific Equals. Inside the type specific Equals, make sure that all the members have the same value.

在接受 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.
}

You probably ought to override GetHashCode as well to make sure that objects that are "equal" have the same hash code.

您可能还应该覆盖 GetHashCode 以确保“相等”的对象具有相同的哈希码。

回答by Richard

  • Define what the identity means.. if reference identity then the default inherited equals will work.
  • If a value type (and thus value identity) you need to define.
  • If a class type, but has value semantics then define.
  • 定义身份的含义.. 如果引用身份,那么默认继承的 equals 将起作用。
  • 如果您需要定义值类型(以及值标识)。
  • 如果是类类型,但具有值语义,则定义。

Likely you want to bothoverride Equals(object) and define Equals(MyType) because the latter avoids boxing. And override the equality operator.

可能要两个重载equals(对象),并定义equals(MyType的),因为后者避免了拳击。并覆盖相等运算符。

.NET Framework Guidelines book (2nd ed) has more coverage.

.NET Framework Guidelines 一书(第 2 版)涵盖了更多内容。

回答by configurator

Lie Daniel L said,

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

回答by baretta

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 )

回答by user1325543

The technique I've used that has worked for me is as follows. Note, I'm only comparing based on a single property (Id) rather that two values. Adjust as needed

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

回答by Steazy

Slight variants of forms already posted by several others...

其他几个人已经发布的表单的轻微变体......

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 );
}

Trying to find a way to implement operator == using Equals to avoid duplicating the value comparison logic... without any redundant tests (ReferenceEquals calls w/ the same parameters) or unnecessary tests (this can't be null in the instance.Equals method) and without any explicit conditionals ("ifs"). More of a mind teaser than anything useful.

试图找到一种方法来实现 operator == 使用 Equals 以避免重复值比较逻辑......没有任何冗余测试(ReferenceEquals 调用具有相同参数)或不必要的测试(这在 instance.Equals 中不能为 null方法)并且没有任何显式条件(“ifs”)。与其说是任何有用的东西,不如说是一种心理暗示。

Closest I can think of is this, but it feelslike it should be possible without an extra method :)

我能想到的最接近的是这个,但感觉没有额外的方法应该是可能的:)

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;
}

Remembering how tedious and error prone this all is (I'm almost sure there's an error in the above code... which still sucks because who wants to subclass a Type justto make equality checks slightly simpler?), going forward I think I'll just create some static methods that handle all the null checks and accept a delegate or require and interface to perform the comparison of values (the only part that really changes Type to Type).

记住这一切是多么乏味和容易出错(我几乎可以肯定上面的代码中有一个错误......这仍然很糟糕,因为谁想要子类化一个 Type只是为了让相等性检查更简单?),我想我将只创建一些静态方法来处理所有空检查并接受委托或要求和接口来执行值的比较(唯一真正将 Type 更改为 Type 的部分)。

It'd be great if we could just add attributes onto the fields/properties/methods that need to be compared and let the compiler/runtime handle all the tedium.

如果我们可以在需要比较的字段/属性/方法上添加属性并让编译器/运行时处理所有乏味的事情,那就太好了。

Also make sure GetHashCode() values are equal for any instances in which which .Equals(object) returns true or crazy shit can happen.

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

回答by mamuesstack

There is also a Fody plugin Equals.Fodythat generates Equals() and GetHashCode() automatically

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

回答by LIDEN

The simple and best way to override Equals looks like:

覆盖 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;
        }
    }

Override the GetHashCode method to allow a type to work correctly in a hash table.

重写 GetHashCode 方法以允许类型在哈希表中正常工作。

回答by Brad Bodily

Using C# 7 and the is type varnamepattern (https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/is#type), provides for a clean Equals(object)that deals with null and type checking using either of the below approaches:

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

// 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;

Obviously, you need to implement at leastone of the following as well:

显然,您还需要至少实现以下其中一项:

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