Интерфейс IEquatable, что делать при проверке на ноль - PullRequest
6 голосов
/ 10 июня 2009

Я реализовал интерфейс IEquatable в классе со следующим кодом.

        public bool Equals(ClauseBE other)
        {
            if (this._id == other._id)
            {
                return true;
            }
            return false;
        }

        public override bool Equals(Object obj)
        {
            if (obj == null)
            {
                return base.Equals(obj);
            }

            if (!(obj is ClauseBE))
            {
                throw new InvalidCastException("The 'obj' argument is not a ClauseBE object.");
            }

            return Equals(obj as ClauseBE);
        }

        public override int GetHashCode()
        {
            return this._id.GetHashCode();
        }

        public static bool operator ==(ClauseBE a, ClauseBE b)
        {
            // cast to object so we call the overloaded Equals function which appropriately checks when b is null.
            return a.Equals(b as object);
        }

        public static bool operator !=(ClauseBE a, ClauseBE b)
        {
            // cast to object so we call the overloaded Equals function which appropriately checks when b is null.
            return !a.Equals(b as object);
        }

Этот код работает очень хорошо в большинстве случаев. Однако следующая проверка выдает исключение в методе перегрузки оператора равенства, поскольку a имеет значение null и, следовательно, не имеет метода Equals.

if(this.Clause != null)
{

}

Каков стандартный способ решения этой проблемы?

EDIT

Я пошел к этому, но это кажется довольно громоздким. Я надеялся, что есть более элегантный способ сделать это.

    public static bool operator ==(ClauseBE a, ClauseBE b)
    {
        if (a as object == null && b as object == null)
        {
            return true;
        }

        if ((a as object == null && b as object != null)
            || (b as object == null && a as object != null))
        {
            return false;
        }

        // cast to object so we call the overloaded Equals function which appropriately checks when b is null.
        return a.Equals(b as object);
    }

    public static bool operator !=(ClauseBE a, ClauseBE b)
    {
        if (a as object == null && b as object == null)
        {
            return false;
        }

        if((a as object == null && b as object != null)
            || (b as object == null && a as object != null))
        {
            return true;
        }

        // cast to object so we call the overloaded Equals function which appropriately checks when b is null.
        return !a.Equals(b as object);
    }

Решение

Спасибо всем. Я получил много хороших советов от всех, я действительно ценю это. Это то, на чем я наконец остановился, это намного элегантнее, чем то, с чего я начал. Весь код одинаков, кроме перегрузок операторов.

public static bool operator ==(ClauseBE a, ClauseBE b)
{
    if (ReferenceEquals(a, null) && ReferenceEquals(b, null))
    {
        return true;
    }

    if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
    {
        return false;
    }

    return a.Equals(b);
}

public static bool operator !=(ClauseBE a, ClauseBE b)
{
    return !(a == b);
}

Ответы [ 8 ]

5 голосов
/ 10 июня 2009

Мне всегда было проще написать статический оператор с нулевой обработкой, а переопределение Equals вызывает перегруженный оператор с «this» в качестве одного из параметров.

С Рекомендации по перегрузке Equals () и оператора == (Руководство по программированию в C #)

//add this code to class ThreeDPoint as defined previously
//
public static bool operator ==(ThreeDPoint a, ThreeDPoint b)
{
    // If both are null, or both are same instance, return true.
    if (System.Object.ReferenceEquals(a, b))
    {
        return true;
    }

    // If one is null, but not both, return false.
    if (((object)a == null) || ((object)b == null))
    {
        return false;
    }

    // Return true if the fields match:
    return a.x == b.x && a.y == b.y && a.z == b.z;
}

public static bool operator !=(ThreeDPoint a, ThreeDPoint b)
{
    return !(a == b);
}
2 голосов
/ 10 июня 2009

Так ReSharper создает операторы равенства и реализует IEquatable<T>, чему я, конечно, слепо доверяю; -)

public class ClauseBE : IEquatable<ClauseBE>
{
    private int _id;

    public bool Equals(ClauseBE other)
    {
        if (ReferenceEquals(null, other))
            return false;
        if (ReferenceEquals(this, other))
            return true;
        return other._id == this._id;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj))
            return false;
        if (ReferenceEquals(this, obj))
            return true;
        if (obj.GetType() != typeof(ClauseBE))
            return false;
        return Equals((ClauseBE)obj);
    }

    public override int GetHashCode()
    {
        return this._id.GetHashCode();
    }

    public static bool operator ==(ClauseBE left, ClauseBE right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(ClauseBE left, ClauseBE right)
    {
        return !Equals(left, right);
    }
}
1 голос
/ 10 июня 2009

Другие ответы дают хорошие решения для общей проблемы.

Однако ваш собственный код можно упростить до относительно простого решения ...

Во-первых, в начале работы вашего == оператора у вас есть это:

    // First test
    if (a as object == null && b as object == null)
    {
        return true;
    }

Это квалифицируется как "слишком усердная работа".

Если ClauseBE является ссылочным типом, то вам нужно только сравнить с null - «as object» является избыточным; в равной степени, если ClauseBE является типом значения, то оно никогда не может быть null.

Предполагая, что ClauseBE является ссылочным типом (наиболее вероятный случай), вы можете упростить это - обратите внимание, что мы используем Object.Equals(), чтобы избежать бесконечной рекурсии и выброса стека.

    // First test
    if (Object.Equals(a, null) && Object.Equals(b, null))
    {
        return true;
    }

Одним из полезных ярлыков является использование Object.ReferenceEquals() - которое обрабатывает нули для вас.

Так что вы могли бы написать это вместо:

    // First test
    if (Object.ReferenceEquals(a, b))
    {
        return true;
    }

с бонусом, что это также обрабатывает случай, когда a и b являются одинаковыми точными объектами.

Как только вы пройдете тест Object.ReferenceEquals(), вы поймете, что a и b различны.

Итак, ваш следующий тест:

    // Second test
    if ((a as object == null && b as object != null)
        || (b as object == null && a as object != null))
    {
        return false;
    }

можно упростить - поскольку вы знаете, что если a равно нулю, b не может быть равно нулю и т. Д.

    // Second test
    if (Object.Equals(a, null) || Object.Equals(b, null))
    {
        return false;
    }

Если этот тест не пройден, то вы знаете, что a и b различны, и что ни один из них не равен нулю. Хорошее время для вызова вашего переопределенного Equals().

    // Use the implementation of Equals() for the rest
    return a.Equals(b as object);
1 голос
/ 10 июня 2009

Я думаю, что это немного менее громоздко, чем приведение к Object перед проверкой на null:

ReferenceEquals(a, null)
1 голос
/ 10 июня 2009

Проверить на ноль и вернуть ложь. Равные всегда должны быть ложными, если один из операндов равен нулю;

0 голосов
/ 19 июля 2009

Я предпочитаю выполнять всю логику сравнения в методе Equals (T) и оставить перегрузку оператора "if this or that null, else ..." в каркасе.

Единственная сложность в переопределении перегрузок операторов заключается в том, что вы больше не можете использовать эти операторы в своей реализации Equals, например, для сравнения с null. Вместо этого object.ReferenceEquals может использоваться для достижения того же эффекта.

Следуя примеру TwoDPoint в статье MSDN Руководство по переопределению Equals () и Operator == , этот шаблон я генерирую при реализации равенства значений для типов:

public override bool Equals( object obj ) {
  // Note: For value types, would use:
  // return obj is TwoDPoint && this.Equals( (TwoDPoint)obj );
  return this.Equals( obj as TwoDPoint );
}

public bool Equals( TwoDPoint other ) {
  // Note: null check not needed for value types.
  return !object.ReferenceEquals( other, null )
      && EqualityComparer<int>.Default.Equals( this.X, other.X )
      && EqualityComparer<int>.Default.Equals( this.Y, other.Y );
}

public static bool operator ==( TwoDPoint left, TwoDPoint right ) {
  // System.Collections.Generic.EqualityComparer<T> will perform the null checks 
  //  on the operands, and will call the Equals overload if necessary.
  return EqualityComparer<TwoDPoint>.Default.Equals( left, right );
}

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

Приведенная выше форма является самой безопасной реализацией, поскольку она просто передает проверки на равенство полей в структуру и не требует знания о том, перегружают ли поля операторы равенства. Это прекрасно, если вы знаете, что существует перегрузка:

public bool Equals( TwoDPoint other ) {
  return !object.ReferenceEquals( other, null )
      && this.X == other.X
      && this.Y == other.Y;
}

Вы также можете заменить вызовы EqualityComparer<T> в перегрузках оператора вызовами статического метода object.Equals при сравнении ссылочных типов или в тех случаях, когда типы значений бокса не имеют значения :

public static bool operator ==( TwoDPoint left, TwoDPoint right ) {
  return object.Equals( left, right );
}

public static bool operator !=( TwoDPoint left, TwoDPoint right ) {
  return !object.Equals( left, right );
}

См. Также Каков наилучший алгоритм для переопределенного GetHashCode? для реализации GetHashCode.

0 голосов
/ 10 июня 2009

Я использовал следующий подход, и мне показалось, что он хорошо работает. Фактически, Решарпер предлагает такой подход.

public bool Equals(Foo pFoo)
{
        if (pFoo == null)
            return false;
        return (pFoo.Id == Id);
}

public override bool Equals(object obj)
{
        if (ReferenceEquals(obj, this))
            return true;

        return Equals(obj as Foo);
}
0 голосов
/ 10 июня 2009
public class Foo : IEquatable<Foo>
{
    public Int32 Id { get; set; }

    public override Int32 GetHashCode()
    {
        return this.Id.GetHashCode();
    }

    public override Boolean Equals(Object obj)
    {
        return !Object.ReferenceEquals(obj as Foo, null)
            && (this.Id == ((Foo)obj).Id);

        // Alternative casting to Object to use == operator.
        return ((Object)(obj as Foo) != null) && (this.Id == ((Foo)obj).Id);
    }

    public static Boolean operator ==(Foo a, Foo b)
    {
        return Object.Equals(a, b);
    }

    public static Boolean operator !=(Foo a, Foo b)
    {
        return !Object.Equals(a, b);
    }

    public Boolean Equals(Foo other)
    {
        return Object.Equals(this, other);
    }
}
...