Как проверить наличие нулей в перегрузке оператора '==' без бесконечной рекурсии?

114

Следующее вызовет бесконечную рекурсию в методе перегрузки оператора ==.

    Foo foo1 = null;
    Foo foo2 = new Foo();
    Assert.IsFalse(foo1 == foo2);

    public static bool operator ==(Foo foo1, Foo foo2) {
        if (foo1 == null) return foo2 == null;
        return foo1.Equals(foo2);
    }

Как проверить наличие нулей?

Эндрю Джонс
источник

Ответы:

139

Использование ReferenceEquals:

Foo foo1 = null;
Foo foo2 = new Foo();
Assert.IsFalse(foo1 == foo2);

public static bool operator ==(Foo foo1, Foo foo2) {
    if (object.ReferenceEquals(null, foo1))
        return object.ReferenceEquals(null, foo2);
    return foo1.Equals(foo2);
}
Абэ Хайдебрехт
источник
Это решение не работает дляAssert.IsFalse(foo2 == foo1);
FIL
А что foo1.Equals(foo2)значит, если, например, я хочу foo1 == foo2только если foo1.x == foo2.x && foo1.y == foo2.y? Разве это не ответ, игнорируя случай, где, foo1 != nullно foo2 == null?
Daniel
Примечание. То же решение с более простым синтаксисом:if (foo1 is null) return foo2 is null;
Рем
20

Привести к объекту в методе перегрузки:

public static bool operator ==(Foo foo1, Foo foo2) {
    if ((object) foo1 == null) return (object) foo2 == null;
    return foo1.Equals(foo2);
}
Эндрю Джонс
источник
1
Именно. Оба (object)foo1 == nullили foo1 == (object)nullперейдут к встроенной перегрузке, ==(object, object)а не к пользовательской перегрузке ==(Foo, Foo). Это похоже на разрешение перегрузки методов.
Jeppe Stig Nielsen
2
Для будущих посетителей - принятый ответ - это функция, которая выполняет == объекта. Это в основном то же самое, что и принятый ответ, с одним недостатком: ему требуется приведение. Таким образом, аккредитованный ответ лучше.
Mafii 08
1
@Mafii Приведение - это чисто операция времени компиляции. Поскольку компилятор знает, что приведение не может завершиться ошибкой, ему не нужно ничего проверять во время выполнения. Различия между методами полностью эстетичны.
Servy
8

Используйте ReferenceEquals. На форумах MSDN :

public static bool operator ==(Foo foo1, Foo foo2) {
    if (ReferenceEquals(foo1, null)) return ReferenceEquals(foo2, null);
    if (ReferenceEquals(foo2, null)) return false;
    return foo1.field1 == foo2.field2;
}
Джон Адамс
источник
4

Пытаться Object.ReferenceEquals(foo1, null)

В любом случае я бы не рекомендовал перегружать ==оператора; его следует использовать для сравнения ссылок и использовать Equalsдля «семантических» сравнений.

Сантьяго Палладино
источник
4

Если я переопределил bool Equals(object obj)и хочу, чтобы оператор ==и Foo.Equals(object obj)возвращал одно и то же значение, я обычно реализую !=оператор следующим образом:

public static bool operator ==(Foo foo1, Foo foo2) {
  return object.Equals(foo1, foo2);
}
public static bool operator !=(Foo foo1, Foo foo2) {
  return !object.Equals(foo1, foo2);
}

Затем оператор ==после выполнения всех проверок на null для меня в конечном итоге вызовет то, foo1.Equals(foo2)что я переопределил, чтобы выполнить фактическую проверку, равны ли они.

Hallgrim
источник
Это кажется очень подходящим; глядя на реализацию Object.Equals(Object, Object)бок о бок Object.ReferenceEquals(Object, Object), довольно ясно, что он Object.Equals(Object, Object)делает все, как предлагается в других ответах из коробки. Почему бы не использовать это?
tne
@tne Потому что нет смысла перегружать ==оператор, если все, что вам нужно, - это поведение по умолчанию. Вы должны выполнять перегрузку только тогда, когда вам нужно реализовать собственную логику сравнения, т. Е. Нечто большее, чем проверка эталонного равенства.
Дэн Бечард
@Dan, я уверен, что вы неправильно поняли мое замечание; в контексте, где уже установлено, что перегрузка ==желательна (вопрос подразумевает это), я просто поддерживаю этот ответ, предполагая, что Object.Equals(Object, Object)другие уловки, такие как использование ReferenceEqualsили явное приведение типов, становятся ненужными (таким образом, «почему бы не использовать это?», «это» быть Equals(Object, Object)). Даже если вы не связаны, ваша точка зрения также верна, и я бы пошел дальше: только перегрузку ==для объектов, которые мы можем классифицировать как «объекты-значения».
tne
@tne Основное отличие состоит в том, что Object.Equals(Object, Object)в свою очередь вызывает Object.Equals (Object), который является виртуальным методом, который, вероятно, переопределяет Foo. Тот факт, что вы ввели виртуальный вызов в свою проверку на равенство, может повлиять на способность компилятора оптимизировать (например, встраивать) эти вызовы. Это, вероятно, незначительно для большинства целей, но в некоторых случаях небольшие затраты на оператор равенства могут означать огромные затраты на циклы или сортированные структуры данных.
Дэн Бечард
@tne Для получения дополнительной информации о тонкостях оптимизации вызовов виртуальных методов см. stackoverflow.com/questions/530799/… .
Дэн Бечард
3

Если вы используете C # 7 или новее, вы можете использовать сопоставление с нулевым постоянным шаблоном:

public static bool operator==(Foo foo1, Foo foo2)
{
    if (foo1 is null)
        return foo2 is null;
    return foo1.Equals(foo2);
}

Это дает вам немного более аккуратный код, чем один вызывающий объект .ReferenceEquals (foo1, null)

Яцекбе
источник
2
илиpublic static bool operator==( Foo foo1, Foo foo2 ) => foo1?.Equals( foo2 ) ?? foo2 is null;
Данко Дурбич
3

В этом случае есть более простой способ проверки null:

if (foo is null)

Это оно!

Эта функция была представлена ​​в C # 7

Рето Мессерли
источник
1

Мой подход - делать

(object)item == null

на что я полагаюсь на objectсобственный оператор равенства, который не может ошибиться. Или собственный метод расширения (и перегрузка):

public static bool IsNull<T>(this T obj) where T : class
{
    return (object)obj == null;
}

public static bool IsNull<T>(this T? obj) where T : struct
{
    return !obj.HasValue;
}

или для обработки большего количества дел может быть:

public static bool IsNull<T>(this T obj) where T : class
{
    return (object)obj == null || obj == DBNull.Value;
}

Ограничение предотвращает использование IsNullтипов значений. Теперь это так же мило, как звонить

object obj = new object();
Guid? guid = null; 
bool b = obj.IsNull(); // false
b = guid.IsNull(); // true
2.IsNull(); // error

Это означает, что у меня есть один последовательный / не подверженный ошибкам стиль проверки на нули повсюду. Я также обнаружил, что (object)item == nullэто очень-очень-очень немного быстрееObject.ReferenceEquals(item, null) , но только если это имеет значение (в настоящее время я работаю над чем-то, где я должен все микро-оптимизировать!).

Чтобы увидеть полное руководство по внедрению проверок равенства, см. Что такое «лучшая практика» для сравнения двух экземпляров ссылочного типа?

Навфал
источник
Nitpick: читатели должны следить за своими зависимостями, прежде чем переходить к таким функциям, как сравнение DbNull, ИМО, случаи, когда это не приведет к возникновению проблем, связанных с SRP , довольно редки. Просто указав на запах кода, это вполне могло быть уместным.
tne
0

Статический Equals(Object, Object)метод указывает, равны ли два объекта, objAи objB. Это также позволяет вам тестировать объекты, значение которых соответствует nullравенству. Он сравнивает objAи objBна равенство следующим образом:

  • Он определяет, представляют ли два объекта одну и ту же ссылку на объект. Если они это сделают, метод вернется true. Этот тест эквивалентен вызову ReferenceEqualsметода. Кроме того, если оба objAи objBесть null, метод возвращает true.
  • Он определяет, есть ли objAили objBесть null. Если так, он возвращается false. Если два объекта не представляют одну и ту же ссылку на объект, и ни один из них не является null, он вызывает objA.Equals(objB)и возвращает результат. Это означает, что при objAпереопределении Object.Equals(Object)метода вызывается это переопределение.

.

public static bool operator ==(Foo objA, Foo objB) {
    return Object.Equals(objA, objB);
}
Зак Постен
источник
0

больше отвечая на переопределяющий оператор, как сравнить с null, который перенаправляется здесь как дубликат.

В тех случаях, когда это делается для поддержки объектов-значений, я считаю новую нотацию удобной и хочу убедиться, что есть только одно место, где производится сравнение. Также использование Object.Equals (A, B) упрощает нулевые проверки.

Это приведет к перегрузке ==,! =, Equals и GetHashCode

    public static bool operator !=(ValueObject self, ValueObject other) => !Equals(self, other);
    public static bool operator ==(ValueObject self, ValueObject other) => Equals(self, other);
    public override bool Equals(object other) => Equals(other as ValueObject );
    public bool Equals(ValueObject other) {
        return !(other is null) && 
               // Value comparisons
               _value == other._value;
    }
    public override int GetHashCode() => _value.GetHashCode();

Для более сложных объектов добавьте дополнительные сравнения в Equals и более богатый GetHashCode.

CCondron
источник
0

Для современного сжатого синтаксиса:

public static bool operator ==(Foo x, Foo y)
{
    return x is null ? y is null : x.Equals(y);
}

public static bool operator !=(Foo x, Foo y)
{
    return x is null ? !(y is null) : !x.Equals(y);
}
MR5
источник
-3

Распространенная ошибка в перегруженных операторе == является использование (a == b), (a ==null)или (b == null)для проверки равенства ссылок. Вместо этого это приводит к вызову перегруженного оператора ==, вызывая infinite loop. Используйте ReferenceEqualsили приведите тип к Object, чтобы избежать цикла.

проверь это

// If both are null, or both are same instance, return true.
if (System.Object.ReferenceEquals(a, b))// using ReferenceEquals
{
    return true;
}

// If one is null, but not both, return false.
if (((object)a == null) || ((object)b == null))// using casting the type to Object
{
    return false;
}

справочные руководства по перегрузке Equals () и Operator ==

Башир АЛЬ-МОМАНИ
источник
1
Уже есть несколько ответов со всей этой информацией. Нам не нужна седьмая копия того же ответа.
Servy
-5

Вы можете попробовать использовать свойство объекта и поймать результирующее исключение NullReferenceException. Если свойство, которое вы пробуете, унаследовано или переопределено от Object, то это работает для любого класса.

public static bool operator ==(Foo foo1, Foo foo2)
{
    //  check if the left parameter is null
    bool LeftNull = false;
    try { Type temp = a_left.GetType(); }
    catch { LeftNull = true; }

    //  check if the right parameter is null
    bool RightNull = false;
    try { Type temp = a_right.GetType(); }
    catch { RightNull = true; }

    //  null checking results
    if (LeftNull && RightNull) return true;
    else if (LeftNull || RightNull) return false;
    else return foo1.field1 == foo2.field2;
}
Цифровой Габег
источник
Если у вас много нулевых объектов, обработка исключений может потребовать больших затрат.
Kasprzol
2
Ха-ха, согласен, это не лучший способ. После публикации этого метода я немедленно пересмотрел свой текущий проект, чтобы использовать вместо него ReferenceEquals. Однако, несмотря на то, что он неоптимален, он работает и, следовательно, является правильным ответом на вопрос.
The Digital Gabeg