【问题标题】:Is there a complete IEquatable implementation reference?是否有完整的 IEquatable 实现参考?
【发布时间】:2010-11-21 09:43:42
【问题描述】:

我在这里关于 SO 的许多问题都与 IEquatable 实现有关。我发现它很难正确实现,因为在幼稚的实现中有很多隐藏的错误,而且我找到的关于它的文章也很不完整。我想找到或写一份明确的参考资料,其中必须包括:

  • 如何正确实现 IEquatable
  • 如何正确覆盖 Equals
  • 如何正确覆盖 GetHashCode
  • 如何正确实现 ToString 方法
  • 如何正确实现运算符 ==
  • 如何正确实现运算符 !=

这样完整的参考文献已经存在了吗?

PS:即使MSDN reference 对我来说似乎也有缺陷

【问题讨论】:

    标签: c# .net equals gethashcode iequatable


    【解决方案1】:

    为值类型实现IEquatable<T>

    为值类型实现IEquatable<T> 与为引用类型实现有点不同。假设我们有 Implement-Your-Own-Value-Type 原型,即复数结构。

    public struct Complex
    {
        public double RealPart { get; set; }
        public double ImaginaryPart { get; set; }
    }
    

    我们的第一步是实现IEquatable<T> 并覆盖Object.EqualsObject.GetHashCode

    public bool Equals(Complex other)
    {
        // Complex is a value type, thus we don't have to check for null
        // if (other == null) return false;
    
        return (this.RealPart == other.RealPart)
            && (this.ImaginaryPart == other.ImaginaryPart);
    }
    
    public override bool Equals(object other)
    {
        // other could be a reference type, the is operator will return false if null
        if (other is Complex)
            return this.Equals((Complex)other);
        else
            return false;
    }
    
    public override int GetHashCode()
    {
        return this.RealPart.GetHashCode() ^ this.ImaginaryPart.GetHashCode();
    }
    

    除了操作符之外,我们只需付出很少的努力就能得到正确的实现。添加运算符也是一个简单的过程:

    public static bool operator ==(Complex term1, Complex term2)
    {
        return term1.Equals(term2);
    }
    
    public static bool operator !=(Complex term1, Complex term2)
    {
        return !term1.Equals(term2);
    }
    

    精明的读者会注意到,我们可能应该实现 IEquatable<double>,因为 Complex 数字可以与底层值类型互换。

    public bool Equals(double otherReal)
    {
        return (this.RealPart == otherReal) && (this.ImaginaryPart == 0.0);
    }
    
    public override bool Equals(object other)
    {
        // other could be a reference type, thus we check for null
        if (other == null) return base.Equals(other);
    
        if (other is Complex)
        {
            return this.Equals((Complex)other);
        }
        else if (other is double)
        {
            return this.Equals((double)other);
        }
        else
        {
            return false;
        }
    }
    

    如果我们添加IEquatable<double>,我们需要四个运算符,因为您可以拥有Complex == doubledouble == Complexoperator != 也是如此):

    public static bool operator ==(Complex term1, double term2)
    {
        return term1.Equals(term2);
    }
    
    public static bool operator ==(double term1, Complex term2)
    {
        return term2.Equals(term1);
    }
    
    public static bool operator !=(Complex term1, double term2)
    {
        return !term1.Equals(term2);
    }
    
    public static bool operator !=(double term1, Complex term2)
    {
        return !term2.Equals(term1);
    }
    

    所以你有它,我们以最小的努力为值类型提供了一个正确且有用的实现IEquatable<T>

    public struct Complex : IEquatable<Complex>, IEquatable<double>
    {
    }
    

    【讨论】:

    • 当 other 为 null 时,你的 Equals(Complex other) 实现会抛出异常
    • 当第一个参数为空时,您的运算符实现也会抛出异常
    • @Jader:谢谢,我已经解决了第一个问题。尝试设想第二个可能实际发生的情况,给出错误 CS0037: Cannot convert null to Complex because it is an non-nullable value type。
    • 另外,“null == c1”和“c1 == null”不使用任何Complex的代码,而是使用object.operator==。
    • @user7116:编辑帖子以故意说出与作者本意不同的内容通常是不好的形式。该帖子建议实现Equals,以违反传递性的期望;我不同意这个建议,并希望作者重新考虑,但我不愿意改变故意给出的建议。
    【解决方案2】:

    我相信,对于 .NET 的设计来说,获得像检查对象是否相等这样简单的事情有点棘手。

    对于结构

    1) 实现IEquatable&lt;T&gt;。它显着提高了性能。

    2) 既然你现在有自己的Equals,请覆盖GetHashCode,并与各种相等检查保持一致,也覆盖object.Equals

    3) 重载==!= 运算符不需要认真执行,因为如果您无意中将一个结构与另一个结构等同于==!=,编译器会发出警告,但这样做很好与Equals 方法一致。

    public struct Entity : IEquatable<Entity>
    {
        public bool Equals(Entity other)
        {
            throw new NotImplementedException("Your equality check here...");
        }
    
        public override bool Equals(object obj)
        {
            if (obj == null || !(obj is Entity))
                return false;
    
            return Equals((Entity)obj);
        }
    
        public static bool operator ==(Entity e1, Entity e2)
        {
            return e1.Equals(e2);
        }
    
        public static bool operator !=(Entity e1, Entity e2)
        {
            return !(e1 == e2);
        }
    
        public override int GetHashCode()
        {
            throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
        }
    }
    

    来自 MS:

    大多数引用类型不应重载相等运算符,即使它们覆盖 Equals。

    对我来说,== 感觉像是价值平等,更像是 Equals 方法的语法糖。写a == b 比写a.Equals(b) 直观得多。我们很少需要检查引用相等性。在处理物理对象的逻辑表示的抽象级别中,这不是我们需要检查的。我认为 ==Equals 具有不同的语义实际上可能会令人困惑。我认为首先应该是 == 用于价值平等和 Equals 用于参考(或更好的名称,如 IsSameAs)平等。 我不想在这里认真对待 MS 指南,不仅因为它对我来说不自然,还因为重载 == 不会造成任何重大伤害。 这与不覆盖 non-通用的EqualsGetHashCode 可以反击,因为框架不会在任何地方使用==,除非我们自己使用它。我从不重载==!= 中获得的唯一真正好处是与我无法控制的整个框架的设计保持一致。这确实是一件大事,很遗憾我会坚持下去

    带有引用语义(可变对象)

    1) 覆盖EqualsGetHashCode

    2) 实现IEquatable&lt;T&gt; 不是必须的,但如果你有一个就好了。

    public class Entity : IEquatable<Entity>
    {
        public bool Equals(Entity other)
        {
            if (ReferenceEquals(this, other))
                return true;
    
            if (ReferenceEquals(null, other))
                return false;
    
            //if your below implementation will involve objects of derived classes, then do a 
            //GetType == other.GetType comparison
            throw new NotImplementedException("Your equality check here...");
        }
    
        public override bool Equals(object obj)
        {
            return Equals(obj as Entity);
        }
    
        public override int GetHashCode()
        {
            throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
        }
    }
    

    具有值语义(不可变对象)

    这是棘手的部分。如果不小心,很容易搞砸..

    1) 覆盖EqualsGetHashCode

    2) 重载 ==!= 以匹配 Equals确保它适用于空值

    2) 实现IEquatable&lt;T&gt; 不是必须的,但如果你有一个就好了。

    public class Entity : IEquatable<Entity>
    {
        public bool Equals(Entity other)
        {
            if (ReferenceEquals(this, other))
                return true;
    
            if (ReferenceEquals(null, other))
                return false;
    
            //if your below implementation will involve objects of derived classes, then do a 
            //GetType == other.GetType comparison
            throw new NotImplementedException("Your equality check here...");
        }
    
        public override bool Equals(object obj)
        {
            return Equals(obj as Entity);
        }
    
        public static bool operator ==(Entity e1, Entity e2)
        {
            if (ReferenceEquals(e1, null))
                return ReferenceEquals(e2, null);
    
            return e1.Equals(e2);
        }
    
        public static bool operator !=(Entity e1, Entity e2)
        {
            return !(e1 == e2);
        }
    
        public override int GetHashCode()
        {
            throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
        }
    }
    

    如果您的类可以被继承,请特别注意看看它应该如何处理,在这种情况下,您必须确定基类对象是否可以等于派生类对象。理想情况下,如果没有派生类的对象用于相等性检查,则基类实例可以等于派生类实例,在这种情况下,无需检查基类的通用Equals 中的Type 相等性.

    一般注意不要重复代码。我本可以制作一个通用抽象基类(IEqualizable&lt;T&gt; 左右)作为模板,以便更轻松地重用,但遗憾的是在 C# 中这阻止了我从其他类派生。

    【讨论】:

    • 我知道现在已经有几年了,但正在调查:docs.microsoft.com/en-us/dotnet/api/… ""此外,您应该重载 op_Equality 和 op_Inequality 运算符。这可确保所有相等性测试返回一致的结果。""" 可变对象也不应实现 IEquatable,因为这意味着 hashCode 可能会随着生命周期而改变。
    【解决方案3】:

    阅读 MSDN 后,我很确定正确实施的最佳示例在 IEquatable.Equals Method 页面中。我唯一的偏差如下:

    public override bool Equals(Object obj)
    {
       if (obj == null) return base.Equals(obj);
    
       if (! (obj is Person))
          return false; // Instead of throw new InvalidOperationException
       else
          return Equals(obj as Person);   
    }
    

    对于那些想知道偏差的人,它来自Object.Equals(Object) MSDN 页面:

    Equals 的实现不得抛出异常。

    【讨论】:

    • 另外,实现者应该警惕重载 '==' 并在 Equals(T object) 中意外使用它。我建议使用 Object.ReferenceEquals 在任何 Equals 风格的方法中检查引用相等性。
    • obj == null,不应调用 operator==,因为此时它仍然是一个对象,而不是一个更派生的类。
    【解决方案4】:

    我找到了另一个参考,它是 .NET Anonymous Type 实现。对于具有 int 和 double 作为属性的匿名类型,我反汇编了以下 C# 代码:

    public class f__AnonymousType0
    {
        // Fields
        public int A { get; }
        public double B { get; }
    
        // Methods
        public override bool Equals(object value)
        {
            var type = value as f__AnonymousType0;
            return (((type != null)
                && EqualityComparer<int>.Default.Equals(this.A, type.A))
                && EqualityComparer<double>.Default.Equals(this.B, type.B));
        }
    
        public override int GetHashCode()
        {
            int num = -1134271262;
            num = (-1521134295 * num) + EqualityComparer<int>.Default.GetHashCode(this.A);
            return ((-1521134295 * num) + EqualityComparer<double>.Default.GetHashCode(this.B);
        }
    
        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("{ A = ");
            builder.Append(this.A);
            builder.Append(", B = ");
            builder.Append(this.B);
            builder.Append(" }");
            return builder.ToString();
        }
    }
    

    【讨论】:

      【解决方案5】:

      我只需要从这个类派生出来

      public abstract class DataClass : IEquatable<DataClass>
      {
          public override bool Equals(object obj)
          {
              var other = obj as DataClass;
              return this.Equals(other);
          }
      
          public bool Equals(DataClass other)
          {
              return (!ReferenceEquals(null, other))
                  && this.Execute((self2, other2) =>
                      other2.Execute((other3, self3) => self3.Equals(other3), self2)
                      , other);
          }
      
          public override int GetHashCode()
          {
              return this.Execute(obj => obj.GetHashCode());
          }
      
          public override string ToString()
          {
              return this.Execute(obj => obj.ToString());
          }
      
          private TOutput Execute<TOutput>(Func<object, TOutput> function)
          {
              return this.Execute((obj, other) => function(obj), new object());
          }
      
          protected abstract TOutput Execute<TParameter, TOutput>(
              Func<object, TParameter, TOutput> function,
              TParameter other);
      }
      

      然后像这样实现抽象方法

      public class Complex : DataClass
      {
          public double Real { get; set; }
      
          public double Imaginary { get; set; }
      
          protected override TOutput Execute<TParameter, TOutput>(
              Func<object, TParameter, TOutput> function,
              TParameter other)
          {
              return function(new
              {
                  Real = this.Real,
                  Imaginary = this.Imaginary,
              }, other);
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-31
        • 1970-01-01
        • 2011-04-07
        相关资源
        最近更新 更多