是否有完整的IEquatable实现引用?

Jad*_*ias 46 .net c# equals iequatable gethashcode

我在这里提出的许多问题涉及IEquatable的实现.我发现正确实现起来非常困难,因为在天真的实现中存在许多隐藏的错误,我发现的关于它的文章非常不完整.我想找到或写出一个必须包括的权威参考:

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

这样一个完整的参考已经存在?

PS:即使是MSDN引用对我来说也是有缺陷的

use*_*116 23

实现IEquatable<T>价值类型

实现IEquatable<T>值类型与引用类型略有不同.假设我们有一个Implement-Your-Own-Value-Type原型,一个复数结构.

public struct Complex
{
    public double RealPart { get; set; }
    public double ImaginaryPart { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我们第一步是实现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();
}
Run Code Online (Sandbox Code Playgroud)

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

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

public static bool operator !=(Complex term1, Complex term2)
{
    return !term1.Equals(term2);
}
Run Code Online (Sandbox Code Playgroud)

精明的读者会注意到我们应该实现,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;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我们添加IEquatable<double>,我们需要四个运算符,因为您可以拥有Complex == doubledouble == Complex(和相同operator !=):

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);
}
Run Code Online (Sandbox Code Playgroud)

所以你有了它,只需要很少的努力,我们就可IEquatable<T>以为值类型提供正确而有用的实现:

public struct Complex : IEquatable<Complex>, IEquatable<double>
{
}
Run Code Online (Sandbox Code Playgroud)


naw*_*fal 11

我相信得到一些像检查对象一样简单正确的东西对于.NET的设计来说有点棘手.

对于Struct

1)实施IEquatable<T>.它显着提高了性能.

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...");
    }
}
Run Code Online (Sandbox Code Playgroud)

上课

来自MS:

大多数引用类型不应重载等于运算符,即使它们重写等于.

对我而言,==感觉就像价值平等,更像是Equals方法的语法糖.写作a == b比写作更直观a.Equals(b).我们很少需要检查参考平等.在处理物理对象的逻辑表示的抽象级别中,这不是我们需要检查的.我认为有不同的语义==Equals实际上是混乱的.我认为首先应该是==价值平等和Equals参考(或更好的名称IsSameAs)平等.我不想在这里认真对待MS指南,不仅因为这对我来说不自然,而且因为超载==不会造成任何重大伤害.这与不覆盖非泛型Equals或者GetHashCode可以咬回来的不同,因为框架不会==在任何地方使用,只有当我们自己使用它时.我从获得的唯一的真正的好处不超载==,并!=会与整个框架的设计一致性过,我没有控制权.这确实是一件大事,很遗憾我会坚持下去.

使用引用语义(可变对象)

1)覆盖EqualsGetHashCode.

2)实施IEquatable<T>不是必须的,但如果你有一个,那就很好.

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...");
    }
}
Run Code Online (Sandbox Code Playgroud)

使用值语义(不可变对象)

这是棘手的部分.如果不加以照顾,很容易搞砸..

1)覆盖EqualsGetHashCode.

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

2)实施IEquatable<T>不是必须的,但如果你有一个,那就很好.

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...");
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您的类可以继承,请特别注意它应该如何运行,在这种情况下,您必须确定基类对象是否可以等于派生类对象.理想地,如果用于检查平等没有派生类的对象,那么基类的实例可以等于派生类实例,并在这种情况下,没有必要检查Type在通用平等Equals基类.

一般注意不要重复代码.我本可以创建一个通用的抽象基类(IEqualizable<T>左右)作为模板,以便更容易重用,但遗憾的是在C#中阻止我从其他类派生.


use*_*116 3

在阅读 MSDN 后,我非常确定正确实现的最佳示例位于IEquatable.Equals 方法页面中。我唯一的偏差如下:

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);   
}
Run Code Online (Sandbox Code Playgroud)

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

Equals 的实现不得抛出异常。

  • 另外,实现者应该警惕重载“==”然后意外地在 Equals(T object) 中使用它。我建议使用 Object.ReferenceEquals 来处理任何 Equals 样式方法中的引用相等性检查。 (9认同)