有完整的 IEquatable 实现参考吗?

发布于 2024-08-02 15:16:19 字数 418 浏览 11 评论 0原文

我在这里关于 SO 的许多问题都涉及 IEquatable 的实现。我发现正确实现非常困难,因为幼稚的实现中存在许多隐藏的错误,而且我发现的有关它的文章相当不完整。我想找到或编写一个明确的参考,其中必须包括:

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

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

PS:即使是 MSDN 参考 对我来说似乎也有缺陷

Many of my questions here on SO concerns IEquatable implementation. I found it being extremely difficult to implement correctly, because there are many hidden bugs in the naïve implementation, and the articles I found about it are quite incomplete. I want to find or write a definitive reference which must include:

  • How to implement IEquatable correctly
  • How to override Equals correctly
  • How to override GetHashCode correctly
  • How to implement the ToString method correctly
  • How to implement the operator == correctly
  • How to implement the operator != correctly

Such a complete reference already exists?

PS: Even MSDN reference seems flawed to me

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

怎会甘心 2024-08-09 15:16:19

为值类型实现 IEquatable

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

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

我们的第一步是实现 IEquatable并覆盖 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,因为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,我们需要四个运算符,因为您可以使用 Complex == doubledouble == Complex (并且相同for 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);
}

现在您已经知道了,只需付出最小的努力,我们就为值类型提供了正确且有用的实现 IEquatable

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

Implementing IEquatable<T> for a Value Type

Implementing IEquatable<T> for a value type is a little bit different than for a reference type. Let's assume we have the Implement-Your-Own-Value-Type archetype, a Complex number struct.

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

Our first step would be to implement IEquatable<T> and override Object.Equals and Object.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();
}

With very little effort we have a correct implementation, excepting the operators. Adding the operators is also a trivial process:

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

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

An astute reader would notice that we should probably implement IEquatable<double> since Complex numbers could be interchangeable with the underlying value type.

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

We need four operators if we add IEquatable<double>, because you can have Complex == double or double == Complex (and the same for 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);
}

So there you have it, with minimal effort we have a correct and useful implementation IEquatable<T> for a value type:

public struct Complex : IEquatable<Complex>, IEquatable<double>
{
}
相守太难 2024-08-09 15:16:19

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

对于结构

1) 实现IEquatable。它显着提高了性能。

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 指南,不仅因为它对我来说不自然,而且还因为重载 == 不会造成任何重大伤害。这与不覆盖非泛型 EqualsGetHashCode 不同,后者可能会反噬,因为框架不会在任何地方使用 ==,除非我们自己使用使用它。我从不重载 ==!= 中获得的唯一真正好处是与整个框架设计的一致性,而我没有的控制.这确实是一件大事,所以遗憾的是我会坚持下去

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

1) 覆盖 EqualsGetHashCode

2) 实现 IEquatable 不是必须的,但如果你有一个那就太好了。

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确保它适用于 null

2) 实现 IEquatable 不是必须的,但如果你有一个那就太好了。

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

要特别注意如果您的类可以继承,情况会如何,在这种情况下,您必须确定基类对象是否可以等于派生类对象。理想情况下,如果没有派生类的对象用于相等性检查,则基类实例可以等于派生类实例,在这种情况下,不需要检查泛型 Type 相等性。 code>Equals 基类。

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

I believe getting something as simple as checking objects for equality correct is a bit tricky with .NET's design.

For Struct

1) Implement IEquatable<T>. It improves performance noticeably.

2) Since you're having your own Equals now, override GetHashCode, and to be consistent with various equality checking override object.Equals as well.

3) Overloading == and != operators need not be religiously done since the compiler will warn if you unintentionally equate a struct with another with a == or !=, but its good to do so to be consistent with Equals methods.

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

For Class

From MS:

Most reference types should not overload the equality operator, even if they override Equals.

To me == feels like value equality, more like a syntactic sugar for Equals method. Writing a == b is much more intuitive than writing a.Equals(b). Rarely we'll need to check reference equality. In abstract levels dealing with logical representations of physical objects this is not something we would need to check. I think having different semantics for == and Equals can actually be confusing. I believe it should have been == for value equality and Equals for reference (or a better name like IsSameAs) equality in the first place. I would love to not take MS guideline seriously here, not just because it isn't natural to me, but also because overloading == doesn't do any major harm. That's unlike not overriding non-generic Equals or GetHashCode which can bite back, because framework doesn't use == anywhere but only if we ourself use it. The only real benefit I gain from not overloading == and != will be the consistency with design of the entire framework over which I have no control of. And that's indeed a big thing, so sadly I will stick to it.

With reference semantics (mutable objects)

1) Override Equals and GetHashCode.

2) Implementing IEquatable<T> isn't a must, but will be nice if you have one.

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

With value semantics (immutable objects)

This is the tricky part. Can get easily messed up if not taken care..

1) Override Equals and GetHashCode.

2) Overload == and != to match Equals. Make sure it works for nulls.

2) Implementing IEquatable<T> isn't a must, but will be nice if you have one.

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

Take special care to see how it should fare if your class can be inherited, in such cases you will have to determine if a base class object can be equal to a derived class object. Ideally, if no objects of derived class is used for equality checking, then a base class instance can be equal to a derived class instance and in such cases, there is no need to check Type equality in generic Equals of base class.

In general take care not to duplicate code. I could have made a generic abstract base class (IEqualizable<T> or so) as a template to allow re-use easier, but sadly in C# that stops me from deriving from additional classes.

紫﹏色ふ单纯 2024-08-09 15:16:19

在阅读 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);   
}

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

Equals 的实现不得抛出异常。

Upon reading MSDN, I'm pretty certain the best example of a proper implementation is in the IEquatable.Equals Method page. My only deviation is the following:

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

For those wondering about the deviation, it derives from the Object.Equals(Object) MSDN page:

Implementations of Equals must not throw exceptions.

寄人书 2024-08-09 15:16:19

我找到了另一个参考,它是 .NET 匿名类型实现。对于具有 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();
    }
}

I found another reference, it's the .NET Anonymous Type implementation. For an anonymous type with an int and a double as properties I disassembled the following C# code:

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();
    }
}
梦里兽 2024-08-09 15:16:19

我只需要从这个类派生

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

I only have to derive from this class

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

And then implement the abstract method like this

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);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文