3

我在解决这个问题时遇到了一些问题,所以我想问一下是否有人对这是否是实现自定义不可变类的 Equals 方法和相等/不等式运算符的有效方法有任何反馈。我的程序非常频繁地调用这些运算符,因此我想确保它们正确无误。

class MyObj
{

    public static bool operator ==(MyObj a, MyObj b)
    {
        if (!object.ReferenceEquals(a, null))
            return a.Equals(b);
        else if (!object.ReferenceEquals(b, null))
            return b.Equals(a);
        else
            // both are null
            return true;
    }

    public static bool operator !=(MyObj a, MyObj b)
    {
        if (!object.ReferenceEquals(a, null))
            return !a.Equals(b);
        else if (!object.ReferenceEquals(b, null))
            return !b.Equals(a);
        else
            // both are null
            return false
    }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as MyObj);
    }

    public bool Equals(MyObj obj)
    {
        if (object.ReferenceEquals(obj, null))
            return false;
        else
            return (obj.FieldOne == this.FieldOne &&
                    obj.FieldTwo == this.FieldTwo && ...);
    }

}
4

5 回答 5

2

我将以下代码片段用于引用类型,在我看来,它的重复更少并且感觉更清晰。拥有静态“Equals”方法允许 .NET 语言在没有运算符重载的情况下比较您的实例,而无需在调用实例方法之前测试 null。如果您正在实现相等运算符,如果可以的话,最好使您的类不可变。

class Foo : IEquatable<Foo>
{
    public override bool Equals(object obj)
    {
        return Equals(obj as Foo);
    }

    public bool Equals(Foo other)
    {
        if (object.ReferenceEquals(other, null)) return false;

        // Optional early out
        if (object.ReferenceEquals(this, other)) return true; 

        // Compare fields here
    }

    public static bool Equals(Foo a, Foo b)
    {
        if (ReferenceEquals(a, null)) return ReferenceEquals(b, null);
        return a.Equals(b);
    }

    public static bool operator ==(Foo a, Foo b)
    {
        return Equals(a, b);
    }

    public static bool operator !=(Foo a, Foo b)
    {
        return !Equals(a, b);
    }
}
于 2009-10-31T16:20:31.543 回答
2

我注意到一些事情:

  • 因为您正在覆盖Equals,所以您也应该覆盖GetHashCode
  • 由于您的Equals(MyObj)方法是整个IEquatable<MyObj>接口的有效实现,MyObj因此确实应该实现该接口。这也将允许Dictionary<>等直接利用您的Equals(MyObj)方法,而不是通过Equals(object).

我也完全同意 Trillian 的替代实现,除了我会a != b直接实现!(a == b)而不是!Equals(a, b). (当然是微不足道的区别。)

于 2009-10-31T16:26:12.520 回答
0

基本上是的,但是有一个错误需要纠正。

Equals(object)方法调用自身而不是调用该Equals(MyObj)方法,从而导致一个永恒的循环。它应该是:

public override bool Equals(object obj) {
   MyObj other = obj as MyObj;
   return this.Equals(other);
}

或者简单地说:

public override bool Equals(object obj) {
   return this.Equals(obj as MyObj);
}

此外,您可以将不等式运算符简化为:

public static bool operator !=(MyObj a, MyObj b) {
   return !(a == b);
}
于 2009-10-31T16:23:43.507 回答
0

如果您正在寻找效率,我建议使用它而不是object.ReferenceEquals(foo, null)

(object)foo == null

这实际上是等效的,但避免了函数调用。

我也喜欢IEquatable<T>在我所有的覆盖类型中实现Equals。对于引用类型,我然后转发Equals(object)Equals(Foo).

public override bool Equals(object other){return Equals(other as Foo);}

运算符重载可以简化为:

public static bool operator==(Foo a, Foo b){
    if((object)a == null)
        return (object)b == null;
    return a.Equals(b);
}
public static bool operator!=(Foo a, Foo b){
    return !(a == b);
}

但是,如果需要绝对的效率,则可能值得在这些函数中进行一些代码重复以避免额外的函数调用,但与使用(object)foo == null代替的不同的是object.ReferenceEquals(foo, null),避免函数调用需要额外的代码来维护,因此小幅收益可能不会值得。

于 2009-10-31T16:32:55.573 回答
0

我更愿意将所有“如果这是 null 则执行其他操作......”逻辑留给框架:

class MyObj : IEquatable<MyObj> {

  public static bool operator ==( MyObj left, MyObj right ) {
    return EqualityComparer<MyObj>.Default.Equals( left, right );
  }

  public static bool operator !=( MyObj left, MyObj right ) {
    return !EqualityComparer<MyObj>.Default.Equals( left, right );
  }

  public override bool Equals( object obj ) {
    return this.Equals( obj as MyObj );
  }

  public bool Equals( MyObj other ) {
    return !object.ReferenceEquals( other, null )
        && obj.FieldOne == this.FieldOne
        && obj.FieldTwo == this.FieldTwo
        && ...
        ;
  }

  ...

}

另请参阅覆盖 GetHashCode 的最佳算法是什么?用于实施GetHashCode.

于 2010-10-09T16:03:40.797 回答