2010-08-17 14 views
32

¿El código que utiliza el static Object.Equals para comprobar nulo es más robusto que el código que utiliza el operador == o regular Object.Equals? ¿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)?Iguales (artículo, nulo) o elemento == nulo

En otras palabras, es la siguiente:

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

más robusto que esto:

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

Personalmente encuentro este último sintaxis 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?

Respuesta

43

No hay una respuesta simple para esta pregunta. Cualquiera que diga siempre usar 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. Dados dos instancias de objeto a y b, se podría escribir:

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

Todo ello podría hacer cosas diferentes!

Object.Equals(a,b) será (por defecto) realizar la comparación de igualdad de referencia sobre los tipos de referencia y comparación bit a bit de los tipos de valor.A partir de la documentación de MSDN:

La implementación predeterminada de Iguales apoya la igualdad de referencia para tipos de referencia, y la igualdad bit a bit de los tipos de valor. La igualdad de referencia significa que las referencias de objeto que son comparadas se refieren al mismo objeto. Igualdad por bit significa que los objetos que se comparan tienen la misma representación binaria de .

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

Tenga en cuenta el último párrafo anterior ... hablaremos de esto un poco más adelante.

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

a.Equals(b) llama al método instancia virtual de Object, que el tipo de a podría reemplazar a 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 a.

a == b invoca el operador sobrecargado estática del tiempo de compilación ** * Tipo de a. Si la implementación de ese operador invoca métodos de instancia en a o b, también puede depender de los tipos de tiempo de ejecución de los parámetros. Dado que el envío se basa en los tipos en la expresión, lo siguiente puede dar 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; 

Así que, sí, no es la vulnerabilidad de comprobación de valores nulos utilizando 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 referencia/igualdad de bits, es posible que un tipo anule el método del 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 pides? ¿Esto puede terminar ejecutando el código de usuario? Bueno, resulta que la respuesta es SÍ. La implementación de Object.Equals(a,b) amplía a algo a lo largo de las líneas de:

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

Puede probar esto por sí 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) para ejecutar código de usuario cuando ninguno de los tipos en la llamada son 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 para llamar. Un comentario aquí, sin embargo: Microsoft no documenta oficialmente el comportamiento interno de Object.Equals(a,b). Si necesita un gaurantee revestido de comparar una referencia a nula y sin ningún otro tipo de código en ejecución hierro, desea Object.ReferenceEquals():

Object.ReferenceEquals(item, null); 

Este método hace que la intención extremently clara - usted está esperando específicamente que el resultado sea la comparación de dos referencias para la igualdad de referencia. La ventaja aquí sobre el uso de algo así como Object.Equals(a,null), es que es menos probable que alguien va a llegar más tarde y decir:

"Hey, esto es incómodo, vamos a sustituirlo por: a.Equals(null) o a == null

que potencialmente puede ser diferente.

vamos a inyectar algo de pragmatismo aquí, sin embargo. hasta ahora hemos hablado de la posibilidad de que diferentes modalidades de comparación para producir resultados diferentes. Si bien esto es ciertamente el caso, hay ciertos tipos de donde es seguro para wr ite a == null. Las clases incorporadas de .NET como String y Nullable<T> tienen una semántica bien definida para la comparación. Además, son sealed, evitando cualquier cambio en su comportamiento a través de la herencia. La siguiente es bastante común (y correcta):

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

Es innecesario (y feo) para escribir:

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

Así que en ciertos casos limitados, utilizando == es seguro y apropiado.

+0

Pensé que mi respuesta (la respuesta de Microsoft por proxy) es una respuesta bastante simple. – mquander

+4

Preguntas como: * "debería siempre/nunca hacer X" * implican una falta de conocimiento sobre los matices del tema en cuestión. Sentí que un poco más de detalle sería útil aquí para aclarar por qué no creo que una respuesta simple sea significativa. – LBushkin

+0

El método estático 'object.Equals (a, b)' puede, de hecho, llamar al método 'Equals' sobrecargado/virtual en el objeto' a'. Si recuerdo correctamente, hace algo como '((objeto) a == (objeto) b) || (a! = null && b! = null && a.Equals (b)) '. (Esto es irrelevante cuando se compara con 'null', que es lo que el OP pregunta, pero es relevante para el caso general.) – LukeH

4

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

+0

Es posible que alguien sobrecargue el operador '==' sin anular 'Equals'. Este sería un mal diseño, pero es completamente posible que la semántica de la comparación de igualdad difiera entre los dos.Además, si el operador '==' está sobrecargado, puede hacer algo totalmente diferente del método 'Objects.Equals()' incorporado, que creo que solo verifica la igualdad de referencia. – LBushkin

2

El framework guidelines sugieren que el tratamiento de Equals como la igualdad de valor (comprobación para ver si dos objetos representan la misma información, es decir, propiedades que comparan), y == como la igualdad de referencia, con la excepción de los objetos inmutables, para lo cual se debe probablemente anulación == para ser igualdad de valor.

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

+2

Las directrices de Microsoft NO abordan el problema de los nulos, que es de lo que se trata la pregunta. Lo que es más importante, mientras que "myString == null" es una prueba segura, "myString.Equals (null)" causará una excepción cuando myString sea nulo. Además, las directrices de Microsoft ni siquiera mencionan la diferencia entre Object.Equals (myObject, b) y myObject.Equals (b). El primero es robusto; este último da una excepción si myObject es nulo. Entonces, aunque el enlace que proporcione es útil, NO es una respuesta a la pregunta del afiche. – ToolmakerSteve

1

En referencia a "... escribir código que manejará objetos fuera del control del autor ...", señalaría que tanto el Object.Equals estático 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 el momento de la compilación en función del 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.

+0

** Esta afirmación no es del todo correcta. ** Es posible que la implementación del operador '==' en un tipo llame a un método virtual en una de las instancias involucradas. Lo que significa que * en realidad es posible * que sucedan cosas que no se basan en los tipos de la expresión, sino más bien en los tipos de tiempo de ejecución involucrados. – LBushkin

+0

Y lo mismo se aplica al método estático 'object.Equals (a, b)' también, que hace algo como '((object) a == (object) b) || (a! = null && b! = null && a.Equals (b)) 'es decir, podría llamar al método virtual' Equals' en el objeto 'a'. – LukeH

+0

@LBushkin Su punto es válido, sin embargo, eso no es igual (perdón por el juego de palabras) mi declaración es "no del todo correcta". –

1

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

ReferenceEquals(a, b)

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

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

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

que devuelve false.Por el contrario:

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

y

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

ambos devuelven true.

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


Cuando se desea probar igualdad (mismo contenido):

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

  • Si se rechaza (si el tipo de variable a no define el operador "=="), utilice "Object.Equals(a, b)".

  • SI usted está dentro de la lógica en un se sabe que no puede ser nulo, entonces puede usar la más legible "a.Equals(b)". 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 hacer frente a la pregunta original:

Q: ¿Son estos susceptible de ser anulado de alguna clase, con código que no maneja correctamente nula, lo que resulta en 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ía usted? 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 detallada A: Object.Equals(a, b) es más probable que funcione en 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.

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

Cuestiones relacionadas