Это хорошая / эффективная идиома для реализации операторов равенства и равенства / неравенства? - PullRequest
3 голосов
/ 31 октября 2009

У меня было несколько проблем с получением этого права, поэтому я хотел спросить, есть ли у кого-нибудь отзывы о том, является ли это эффективным способом реализации метода 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 ]

2 голосов
/ 31 октября 2009

Некоторые вещи, которые я замечаю:

  • Поскольку вы переопределяете Equals, вам также следует переопределить GetHashCode.
  • Поскольку ваш метод Equals(MyObj) является допустимой реализацией для всего интерфейса IEquatable<MyObj>, MyObj действительно должен реализовать этот интерфейс. Это также позволит Dictionary<> и тому подобному непосредственно использовать ваш метод Equals(MyObj) вместо того, чтобы проходить через Equals(object).

Также я полностью согласен с альтернативной реализацией Trillian, за исключением того, что я бы реализовал a != b непосредственно как !(a == b) вместо !Equals(a, b). (Банальная разница конечно.)

2 голосов
/ 31 октября 2009

Я использую следующий фрагмент кода для ссылочных типов, который, по моему мнению, имеет меньшее дублирование и выглядит чище. Наличие статического метода «Равно» позволяет языкам .NET без перегрузки операторов сравнивать ваши экземпляры без необходимости проверять нулевое значение перед вызовом метода экземпляра. Если вы реализуете операторы равенства, было бы также лучше сделать ваш класс неизменным, если вы можете.

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);
    }
}
0 голосов
/ 09 октября 2010

Я предпочитаю оставить всю логику "если это пустое значение, тогда сделайте это еще ...":

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.

0 голосов
/ 31 октября 2009

Если вы ищете эффективность, я рекомендую использовать это вместо 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), избегая Для вызова функции требуется дополнительный код, поэтому небольшой выигрыш может не стоить.

0 голосов
/ 31 октября 2009

В основном да, но есть ошибка, чтобы исправить.

Метод 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);
}
...