¿Cómo verifico valores nulos en una sobrecarga de operador '==' sin recursividad infinita?

113

Lo siguiente causará una recursividad infinita en el método de sobrecarga del operador ==

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

¿Cómo verifico si hay nulos?

Andrew Jones
fuente

Respuestas:

138

Utilizar 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);
}
Abe Heidebrecht
fuente
Esta solución no funciona paraAssert.IsFalse(foo2 == foo1);
FIL
¿Y qué foo1.Equals(foo2)significa si, por ejemplo, quiero foo1 == foo2sólo si foo1.x == foo2.x && foo1.y == foo2.y? ¿No es esto responder ignorando el caso dónde foo1 != nullpero foo2 == null?
Daniel
Nota: La misma solución con una sintaxis más simple:if (foo1 is null) return foo2 is null;
Rem
20

Transmitir a objeto en el método de sobrecarga:

public static bool operator ==(Foo foo1, Foo foo2) {
    if ((object) foo1 == null) return (object) foo2 == null;
    return foo1.Equals(foo2);
}
Andrew Jones
fuente
1
Exactamente. Ambos (object)foo1 == nullo foo1 == (object)nullirán a la sobrecarga incorporada ==(object, object)y no a la sobrecarga definida por el usuario ==(Foo, Foo). Es como la resolución de sobrecargas en los métodos.
Jeppe Stig Nielsen
2
Para los futuros visitantes, la respuesta aceptada es una función, que ejecuta el == de objeto. Esta es básicamente la misma que la respuesta aceptada, con un inconveniente: necesita un yeso. La respuesta aceptada es, por tanto, superior.
Mafii
1
@Mafii La conversión es puramente una operación de tiempo de compilación. Dado que el compilador sabe que la conversión no puede fallar, no necesita verificar nada en tiempo de ejecución. Las diferencias entre los métodos son completamente estéticas.
Servicio
8

Utilice ReferenceEquals. Desde los foros de 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;
}
Jon Adams
fuente
4

Tratar Object.ReferenceEquals(foo1, null)

De todos modos, no recomendaría sobrecargar al ==operador; debe usarse para comparar referencias y usarse Equalspara comparaciones "semánticas".

Santiago Palladino
fuente
4

Si he anulado bool Equals(object obj)y quiero que el operador ==y Foo.Equals(object obj)devuelva el mismo valor, generalmente implemento el !=operador de esta manera:

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

==Luego, el operador , después de hacer todas las comprobaciones nulas, terminará llamando foo1.Equals(foo2)que he anulado para hacer la verificación real si los dos son iguales.

Hallgrim
fuente
Esto se siente muy apropiado; Al observar la implementación de Object.Equals(Object, Object)lado a lado Object.ReferenceEquals(Object, Object), está bastante claro que Object.Equals(Object, Object)hace todo como se sugiere en las otras respuestas listas para usar. ¿Por qué no usarlo?
TNE
@tne Porque no tiene sentido sobrecargar al ==operador si todo lo que desea es un comportamiento predeterminado. Solo debe sobrecargar cuando necesite implementar una lógica de comparación personalizada, es decir, algo más que una verificación de igualdad de referencia.
Dan Bechard
@Dan Estoy seguro de que entendiste mal mi comentario; en un contexto en el que ya está establecido que la sobrecarga ==es deseable (la pregunta lo implica) simplemente estoy apoyando esta respuesta sugiriendo que Object.Equals(Object, Object)hace que otros trucos como el uso ReferenceEqualso las conversiones explícitas sean innecesarios (por lo tanto, "¿por qué no usarlo?", "eso" ser Equals(Object, Object)). Incluso si no está relacionado, su punto también es correcto, y yo iría más allá: solo la sobrecarga ==para los objetos que podemos clasificar como "objetos de valor".
TNE
@tne La principal diferencia es que Object.Equals(Object, Object)a su vez llama a Object.Equals (Object), que es un método virtual que Foo probablemente anula. El hecho de que haya introducido una llamada virtual en su verificación de igualdad podría afectar la capacidad del compilador para optimizar (por ejemplo, en línea) estas llamadas. Esto probablemente sea insignificante para la mayoría de los propósitos, pero en ciertos casos, un pequeño costo en un operador de igualdad puede significar un costo enorme para los bucles o las estructuras de datos ordenadas.
Dan Bechard
@tne Para obtener más información sobre las complejidades de optimizar las llamadas a métodos virtuales, consulte stackoverflow.com/questions/530799/… .
Dan Bechard
3

Si está utilizando C # 7 o posterior, puede utilizar la coincidencia de patrones constantes nulos:

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

Esto le da un código un poco más ordenado que el que llama al objeto. ReferenceEquals (foo1, null)

jacekbe
fuente
2
opublic static bool operator==( Foo foo1, Foo foo2 ) => foo1?.Equals( foo2 ) ?? foo2 is null;
Danko Durbić
3

En realidad, existe una forma más sencilla de verificar nullen este caso:

if (foo is null)

¡Eso es!

Esta característica se introdujo en C # 7

Reto Messerli
fuente
1

Mi enfoque es hacer

(object)item == null

en el que confío en objectel propio operador de igualdad que no puede fallar. O un método de extensión personalizado (y una sobrecarga):

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

o para manejar más casos, puede ser:

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

La restricción impide los IsNulltipos de valor. Ahora es tan dulce como llamar

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

lo que significa que tengo un estilo consistente / no propenso a errores de verificación de nulos en todo momento. También he descubierto que (object)item == nulles muy, muy ligeramente más rápido queObject.ReferenceEquals(item, null) , pero solo si importa (¡actualmente estoy trabajando en algo en lo que tengo que micro-optimizar todo!).

Para ver una guía completa sobre la implementación de verificaciones de igualdad, consulte ¿Cuál es la "práctica recomendada" para comparar dos instancias de un tipo de referencia?

nawfal
fuente
Nitpick: los lectores deben observar sus dependencias antes de saltar a funciones como comparar DbNull, en mi opinión, los casos en los que esto no generaría problemas relacionados con SRP son bastante raros. Sin embargo, solo señalando el olor del código, podría ser muy apropiado.
TNE
0

El Equals(Object, Object)método estático indica si dos objetos, objAy objB, son iguales. También le permite probar objetos cuyo valor es nullde igualdad. Compara objAy objBpor igualdad de la siguiente manera:

  • Determina si los dos objetos representan la misma referencia de objeto. Si lo hacen, el método regresa true. Esta prueba es equivalente a llamar al ReferenceEqualsmétodo. Además, si ambos objAy objBson null, el método regresa true.
  • Determina si es objAo objBes null. Si es así, vuelve false. Si los dos objetos no representan la misma referencia de objeto y ninguno lo es null, llama objA.Equals(objB)y devuelve el resultado. Esto significa que si objAanula el Object.Equals(Object)método, se llama a esta anulación.

.

public static bool operator ==(Foo objA, Foo objB) {
    return Object.Equals(objA, objB);
}
Zach Posten
fuente
0

respondiendo más al operador anulante cómo comparar con nulo que redirige aquí como un duplicado.

En los casos en que esto se hace para admitir objetos de valor, encuentro que la nueva notación es útil y me gusta asegurarme de que solo haya un lugar donde se realice la comparación. Además, aprovechar Object.Equals (A, B) simplifica las comprobaciones nulas.

Esto sobrecargará ==,! =, Equals y 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();

Para objetos más complicados, agregue comparaciones adicionales en Equals y un GetHashCode más rico.

Condron
fuente
0

Para una sintaxis moderna y condensada:

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
fuente
-3

Un error común en las sobrecargas de operador == es utilizar (a == b), (a ==null)o (b == null)para comprobar la igualdad de referencia. En cambio, esto da como resultado una llamada al operador sobrecargado ==, lo que provoca un infinite loop. Utilice ReferenceEqualso convierta el tipo en Objeto para evitar el bucle.

mira esto

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

Directrices de referencia para sobrecargar Equals () y Operador ==

Basheer AL-MOMANI
fuente
1
Ya hay varias respuestas con toda esta información. No necesitamos una séptima copia de la misma respuesta.
Servicio
-5

Puede intentar usar una propiedad de objeto y capturar la NullReferenceException resultante. Si la propiedad que prueba se hereda o se anula de Object, esto funciona para cualquier clase.

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;
}
El Gabeg digital
fuente
Si tiene muchos objetos nulos, el manejo de excepciones puede ser una gran sobrecarga.
Kasprzol
2
Jaja, estoy de acuerdo en que este no es el mejor método. Después de publicar este método, revisé inmediatamente mi proyecto actual para usar ReferenceEquals en su lugar. Sin embargo, a pesar de ser subóptimo, funciona y, por lo tanto, es una respuesta válida a la pregunta.
The Digital Gabeg