这是实现Equals和平等/不平等运算符的好/有效的习惯用法吗?

时间:2009-10-31 16:11:39

标签: c# operator-overloading equals

我遇到了一些问题,所以我想问一下是否有人对这是否是一种为自定义不可变类实现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 && ...);
    }

}

5 个答案:

答案 0 :(得分: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);
    }
}

答案 1 :(得分:2)

我注意到的一些事情:

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

此外,我完全赞同Trillian的替代实现,除了我将[{1}}直接实现为a != b而不是!(a == b)。 (当然是微不足道的。)

答案 2 :(得分: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);
}

答案 3 :(得分: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而不是{{1避免函数调用需要额外的代码来维护,因此小的增益可能不值得。

答案 4 :(得分:0)

我更愿意将所有“如果这是空的,那就做其他......”逻辑留给框架:

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

  ...

}

另请参阅What is the best algorithm for an overridden GetHashCode?了解GetHashCode