Як перевірити наявність нульових значень при перевантаженні оператора '==' без нескінченної рекурсії?


113

Нижче наведено нескінченну рекурсію методу перевантаження оператора ==

    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);
    }

Як перевірити наявність нулів?

Відповіді:


138

Використання 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?
Даніель

Примітка: те саме рішення з більш простим синтаксисом: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
Для майбутніх відвідувачів - прийнята відповідь - це функція, яка виконує == об’єкта. Це в основному те саме, що прийнята відповідь, з однією недоліком: їй потрібен акторський склад. Таким чином, прихильна відповідь є вищою.
Мафій

1
@Mafii У ролях є суто операція компіляції. Оскільки компілятор знає, що акторський склад не може вийти з ладу, йому не потрібно нічого перевіряти під час виконання. Відмінності між методами абсолютно естетичні.
Сервіс


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);
}

Оператор ==потім, виконавши всі нульові перевірки для мене, в кінцевому підсумку зателефонує, foo1.Equals(foo2)що я відмінив, щоб зробити фактичну перевірку, якщо два рівні.


Це відчуває себе дуже доречно; дивлячись на реалізацію 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) , але тільки якщо це має значення (я зараз працюю над тим, де я повинен все оптимізувати!).

Щоб переглянути повний посібник із здійснення перевірок рівності, див. Що таке "Найкраща практика" для порівняння двох примірників еталонного типу?


Нітпік: Читачі повинні спостерігати за своїми залежностями, перш ніж переходити на такі функції, як порівняння DbNull, IMO випадки, коли це не спричинило б проблеми, пов'язані з 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) спрощує нульові перевірки.

Це перевантажить ==,! =, Дорівнює та 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();

Для більш складних об'єктів додайте додаткові порівняння в рівних та багатший GetHashCode.


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);
}

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

довідкові вказівки щодо перевантаження рівних () та оператора ==


1
На всю цю інформацію вже є багато відповідей. Нам не потрібна сьома копія тієї самої відповіді.
Сервіс

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

Якщо у вас багато нульових об'єктів, обробка виключень може бути великою накладною витратою.
Каспрцол

2
Ха-ха, я згоден, що це не найкращий метод. Після публікації цього методу я негайно переглянув мій поточний проект, щоб замість нього використати ReferenceEquals. Однак, незважаючи на те, що він є неоптимальним, він працює, і тому є вагомою відповіддю на питання.
Цифровий Габег
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.