remarks generate example c# null equals robustness

c# - generate - Iguales(artículo, nulo) o elemento== nulo



params comments c# (5)

¿Es el código que usa los Object.Equals estáticos para comprobar si null es más robusto que el código que usa el operador == o Object.Equals regular ? ¿No son los dos últimos vulnerables a ser anulados de tal manera que la comprobación de null no funciona como se espera (por ejemplo, devolver falsa cuando el valor comparado es nulo)?

En otras palabras, es esto:

if (Equals(item, null)) { /* Do Something */ }

más robusto que esto:

if (item == null) { /* Do Something */ }

Personalmente encuentro que la última sintaxis es más fácil de leer. ¿Debería evitarse al escribir código que manejará objetos fuera del control del autor (por ejemplo, bibliotecas)? ¿Debería siempre evitarse (cuando se busca nulo)? ¿Es esto tan simple?


En referencia a "... escribir código que manejará objetos fuera del control del autor ...", señalaría que tanto Object.Equals estáticos como el operador == son métodos estáticos y, por lo tanto, no pueden ser virtuales / reemplazados. La implementación que se llama se determina en tiempo de compilación según el tipo (s) estático (s). En otras palabras, no hay forma de que una biblioteca externa proporcione una versión diferente de la rutina a su código compilado.


Las directrices marco sugieren que trate Equals como valor equality (verificando si dos objetos representan la misma información, es decir, comparando propiedades), y == como igualdad de referencia, con la excepción de objetos inmutables, para los cuales probablemente debería anular == ser valor igualdad.

Entonces, suponiendo que las pautas se apliquen aquí, elija lo que sea semánticamente sensato. Si está tratando con objetos inmutables, y espera que ambos métodos produzcan resultados idénticos, usaría == para mayor claridad.


if (Equals(item, null)) no es más robusto que if (item == null) , y me resulta más confuso arrancar.


Cuando desee probar IDENTIDAD (la misma ubicación en la memoria):

ReferenceEquals(a, b)

Maneja nulos. Y no es invalidable. 100% seguro.

Pero asegúrese de que realmente quiere la prueba de IDENTIDAD. Considera lo siguiente:

ReferenceEquals(new String("abc"), new String("abc"))

que devuelve false A diferencia de:

Object.Equals(new String("abc"), new String("abc"))

y

(new String("abc")) == (new String("abc"))

ambos regresan true .

Si espera una respuesta true en esta situación, quiere una prueba de IGUALDAD, no una prueba de IDENTIDAD. Ver la siguiente parte.

Cuando desee probar IGUALDAD (mismo contenido):

  • Use " a == b " si el compilador no se queja.

  • Si eso es rechazado (si el tipo de variable a no define el operador "=="), entonces use " Object.Equals(a, b) ".

  • SI se encuentra dentro de la lógica donde se sabe que a no es nulo , ENTONCES puede usar " a.Equals(b) " más legible. Por ejemplo, "this.Equals (b)" es seguro. O si "a" es un campo que se inicializa en el momento de la construcción, y el constructor arroja una excepción si null se pasa como el valor que se utilizará en ese campo.

AHORA, para abordar la pregunta original:

P: ¿Son susceptibles de ser anulados en alguna clase, con código que no maneja null correctamente, dando como resultado una excepción?

A: Sí. La única forma de obtener una prueba de IGUALDAD 100% segura sería realizar una prueba previa de los nulos por su cuenta.

¿Pero deberías? El error estaría en eso (clase hipotética del futuro malo), y sería un tipo directo de falla. Fácil de depurar y corregir (por quienquiera que suministre la clase). Dudo que sea un problema que sucede a menudo, o persiste por mucho tiempo cuando sucede.

Más detallado A: Object.Equals(a, b) es más probable que funcione frente a una clase mal escrita. Si "a" es nulo, la clase Object lo manejará sola, por lo que no hay riesgo allí. Si "b" es nulo, entonces el tipo DINÁMICO (tiempo de ejecución no en tiempo de compilación) de "a" determina qué método se llama "Igual". El método llamado simplemente tiene que funcionar correctamente cuando "b" es nulo. A menos que el método llamado esté muy mal escrito, el primer paso es determinar si "b" es un tipo que entiende.

Entonces Object.Equals(a, b) es un compromiso razonable entre legibilidad / codificación_effort y seguridad.


No hay una respuesta simple para esta pregunta. Cualquiera que diga que siempre usa uno u otro le está dando malos consejos, en mi opinión.

En realidad, hay varios métodos diferentes que puede llamar para comparar instancias de objetos. Dadas dos instancias de objeto a y b , podrías escribir:

  • Object.Equals(a,b)
  • Object.ReferenceEquals(a,b)
  • a.Equals(b)
  • a == b

¡Todos podrían hacer cosas diferentes!

Object.Equals(a,b) (de forma predeterminada) una comparación de igualdad de referencia en tipos de referencia y una comparación bit a bit en tipos de valores. De la documentación de MSDN:

La implementación predeterminada de Equals admite la igualdad de referencia para los tipos de referencia y la igualdad bit a bit para los tipos de valor. La igualdad de referencia significa que las referencias de objetos que se comparan se refieren al mismo objeto. La igualdad de bits significa que los objetos que se comparan tienen la misma representación binaria.

Tenga en cuenta que un tipo derivado puede anular el método Equals para implementar igualdad de valores. La igualdad de valor significa que los objetos comparados tienen el mismo valor pero diferentes representaciones binarias.

Tenga en cuenta el último párrafo anterior ... lo discutiremos un poco más adelante.

Object.ReferenceEquals(a,b) realiza la comparación de igualdad de referencia. Si los tipos pasados ​​son tipos de valores encuadrados, el resultado siempre es false .

a.Equals(b) llama al método de instancia virtual de Object , que el tipo de a podría anular para hacer lo que quiera. La llamada se realiza mediante el despacho virtual, por lo que el código que se ejecuta depende del tipo de tiempo de ejecución de a .

a == b invoca al operador sobrecargado estático del ** tipo de tiempo de compilación * de a . Si la implementación de ese operador invoca métodos de instancia en b , también puede depender de los tipos de tiempo de ejecución de los parámetros. Como el envío se basa en los tipos de la expresión, lo siguiente puede arrojar resultados diferentes:

Frog aFrog = new Frog(); Frog bFrog = new Frog(); Animal aAnimal = aFrog; Animal bAnimal = bFrog; // not necessarily equal... bool areEqualFrogs = aFrog == bFrog; bool areEqualAnimals = aAnimal = bAnimal;

Entonces, sí, existe una vulnerabilidad para verificar nulos usando operator == . En la práctica, la mayoría de los tipos no sobrecarga == - pero nunca hay una garantía.

El método de instancia Equals() no es mejor aquí. Si bien la implementación predeterminada realiza comprobaciones de igualdad de referencia / bit, es posible que un tipo anule el método de miembro Equals() , en cuyo caso se llamará a esta implementación. Una implementación suministrada por el usuario podría devolver lo que quiera, incluso cuando se compara con nulo.

Pero, ¿qué pasa con la versión estática de Object.Equals() que preguntas? ¿Esto puede terminar ejecutando el código de usuario? Bueno, resulta que la respuesta es SÍ. La implementación de Object.Equals(a,b) expande a algo Object.Equals(a,b) :

((object)a == (object)b) || (a != null && b != null && a.Equals(b))

Puedes probar esto por ti mismo:

class Foo { public override bool Equals(object obj) { return true; } } var a = new Foo(); var b = new Foo(); Console.WriteLine( Object.Equals(a,b) ); // outputs "True!"

Como consecuencia, es posible que la declaración: Object.Equals(a,b) ejecute código de usuario cuando ninguno de los tipos en la llamada es null . Tenga en cuenta que Object.Equals(a,b) no llama a la versión de instancia de Equals() cuando cualquiera de los argumentos es nulo.

En resumen, el tipo de comportamiento de comparación que usted obtiene puede variar significativamente, dependiendo del método que elija llamar. Un comentario aquí, sin embargo: Microsoft no documenta oficialmente el comportamiento interno de Object.Equals(a,b) . Si necesita una garantía revestida de hierro de comparar una referencia a nulo sin ningún otro código en ejecución, quiere Object.ReferenceEquals() :

Object.ReferenceEquals(item, null);

Este método hace que la intención sea totalmente clara: esperas específicamente que el resultado sea la comparación de dos referencias para la igualdad de referencia. El beneficio aquí sobre usar algo como Object.Equals(a,null) , es que es menos probable que alguien venga más tarde y diga:

"Oye, esto es incómodo, reemplázalo con: a.Equals(null) or a == null

que potencialmente puede ser diferente.

Inyectemos algo de pragmatismo aquí, sin embargo. Hasta ahora hemos hablado sobre el potencial de diferentes modalidades de comparación para producir resultados diferentes. Si bien este es ciertamente el caso, hay ciertos tipos en los que es seguro escribir a == null . Las clases incorporadas de .NET como String y Nullable<T> tienen una semántica bien definida para la comparación. Además, están sealed , lo que evita cualquier cambio en su comportamiento a través de la herencia. Lo siguiente es bastante común (y correcto):

string s = ... if( s == null ) { ... }

No es necesario (y feo) escribir:

if( ReferenceEquals(s,null) ) { ... }

Entonces, en ciertos casos limitados, usar == es seguro y apropiado.