ternario - validar si un objeto es null c#
Por qué verificar esto!=Nulo? (6)
¿Supongo que estabas mirando la implementación de .NET 3.5? Creo que la implementación de .NET 4 es ligeramente diferente.
Sin embargo, tengo la sospecha de que esto se debe a que es posible llamar incluso a los métodos de instancia virtual de forma virtual en una referencia nula . Posible en IL, eso es. null.Equals(null)
si puedo producir IL que llamaría null.Equals(null)
.
EDITAR: Bien, aquí hay un código interesante:
.method private hidebysig static void Main() cil managed
{
.entrypoint
// Code size 17 (0x11)
.maxstack 2
.locals init (string V_0)
IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: ldnull
IL_0005: call instance bool [mscorlib]System.String::Equals(string)
IL_000a: call void [mscorlib]System.Console::WriteLine(bool)
IL_000f: nop
IL_0010: ret
} // end of method Test::Main
Lo obtuve compilando el siguiente código C #:
using System;
class Test
{
static void Main()
{
string x = null;
Console.WriteLine(x.Equals(null));
}
}
... y luego desmontar con ildasm
y edición. Tenga en cuenta esta línea:
IL_0005: call instance bool [mscorlib]System.String::Equals(string)
Originalmente, eso era callvirt
lugar de call
.
Entonces, ¿qué sucede cuando lo volvemos a armar? Bueno, con .NET 4.0 obtenemos esto:
Unhandled Exception: System.NullReferenceException: Object
reference not set to an instance of an object.
at Test.Main()
Hmm. ¿Qué pasa con .NET 2.0?
Unhandled Exception: System.NullReferenceException: Object reference
not set to an instance of an object.
at System.String.EqualsHelper(String strA, String strB)
at Test.Main()
Ahora que es más interesante ... claramente hemos logrado ingresar a EqualsHelper
, lo que normalmente no hubiéramos esperado.
Suficiente de cadena ... intentemos implementar la igualdad de referencia nosotros mismos, y veamos si podemos obtener null.Equals(null)
para que sea verdadero:
using System;
class Test
{
static void Main()
{
Test x = null;
Console.WriteLine(x.Equals(null));
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public override bool Equals(object other)
{
return other == this;
}
}
El mismo procedimiento que antes: desmontar, cambiar callvirt
para call
, volver a montar y verlo imprimir true
...
Tenga en cuenta que aunque hay otras respuestas que hacen referencia a esta pregunta en C ++ , estamos siendo aún más desviados aquí ... porque estamos llamando a un método virtual de forma no virtual. Normalmente incluso el compilador C ++ / CLI usará callvirt
para un método virtual. En otras palabras, creo que en este caso particular, la única forma de que this
sea nulo es escribiendo el IL a mano.
EDITAR: Acabo de notar algo ... En realidad, no estaba llamando al método correcto en ninguno de nuestros pequeños programas de muestra. Aquí está la llamada en el primer caso:
IL_0005: call instance bool [mscorlib]System.String::Equals(string)
aquí está la llamada en el segundo:
IL_0005: call instance bool [mscorlib]System.Object::Equals(object)
En el primer caso, quise llamar a System.String::Equals(object)
, y en el segundo, quise llamar a Test::Equals(object)
. De esto podemos ver tres cosas:
- Debe tener cuidado con la sobrecarga.
- El compilador de C # emite llamadas al declarante del método virtual, no la anulación más específica del método virtual. IIRC, VB funciona de manera opuesta
-
object.Equals(object)
se complace en comparar una referencia "this" nula
Si agrega un poco de salida de la consola a la anulación de C #, puede ver la diferencia: no se ejecutará a menos que cambie la IL para llamarla de manera explícita, así:
IL_0005: call instance bool Test::Equals(object)
Entonces, ahí estamos. Diversión y abuso de métodos de instancia en referencias nulas.
Si has llegado hasta aquí, también te gustaría ver mi blog sobre cómo los tipos de valor pueden declarar constructores sin parámetros ... en IL.
Ocasionalmente, me gusta pasar un tiempo mirando el código .NET solo para ver cómo se implementan las cosas detrás de escena. Me encontré con esta joya mientras miraba el método String.Equals
través de Reflector.
DO#
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public override bool Equals(object obj)
{
string strB = obj as string;
if ((strB == null) && (this != null))
{
return false;
}
return EqualsHelper(this, strB);
}
ILLINOIS
.method public hidebysig virtual instance bool Equals(object obj) cil managed
{
.custom instance void System.Runtime.ConstrainedExecution.ReliabilityContractAttribute::.ctor(valuetype System.Runtime.ConstrainedExecution.Consistency, valuetype System.Runtime.ConstrainedExecution.Cer) = { int32(3) int32(1) }
.maxstack 2
.locals init (
[0] string str)
L_0000: ldarg.1
L_0001: isinst string
L_0006: stloc.0
L_0007: ldloc.0
L_0008: brtrue.s L_000f
L_000a: ldarg.0
L_000b: brfalse.s L_000f
L_000d: ldc.i4.0
L_000e: ret
L_000f: ldarg.0
L_0010: ldloc.0
L_0011: call bool System.String::EqualsHelper(string, string)
L_0016: ret
}
¿Cuál es el razonamiento para verificar this
contra null
? Tengo que asumir que hay un propósito, de lo contrario, probablemente esto ya habría sido capturado y eliminado.
El código fuente tiene este comentario:
esto es necesario para protegerse contra golpes invertidos y otras llamadas que no usan la instrucción callvirt
La razón es que, de hecho, es posible que this
sea null
. Hay 2 códigos operativos IL que se pueden usar para invocar una función: call y callvirt. La función callvirt hace que CLR realice una comprobación nula al invocar el método. La instrucción de llamada no permite y, por lo tanto, permite que se ingrese un método que es null
.
Suena aterrador? De hecho, es un poco. Sin embargo, la mayoría de los compiladores aseguran que esto no ocurra nunca. La instrucción .call solo se genera cuando null
no es una posibilidad (estoy bastante seguro de que C # siempre usa callvirt).
Sin embargo, esto no es cierto para todos los idiomas y por razones que no sé exactamente que el equipo BCL eligió para fortalecer aún más la clase System.String
en esta instancia.
Otro caso donde esto puede aparecer es en llamadas de búsqueda inversa inversa.
La respuesta corta es que los lenguajes como C # le obligan a crear una instancia de esta clase antes de llamar al método, pero el Framework en sí no lo hace. Hay dos maneras diferentes en CIL para llamar a una función: call
y callvirt
... En términos generales, C # siempre emitirá callvirt
, que requiere que this
no sea nulo. Pero otros lenguajes (C ++ / CLI viene a la mente) podrían emitir call
, que no tienen esa expectativa.
(¹okay, es más como cinco si cuentas calli, newobj, etc., pero hagámoslo simple)
Si el argumento (obj) no se convierte en una cadena, strB será nulo y el resultado será falso. Ejemplo:
int[] list = {1,2,3};
Console.WriteLine("a string".Equals(list));
escribe false
Recuerde que se llama al método string.Equals () para cualquier tipo de argumento, no solo para otras cadenas.
Veamos ... this
es la primera cadena que estás comparando. obj
es el segundo objeto. Entonces parece que es una optimización de géneros. Primero está lanzando obj
a un tipo de cadena. Y si eso falla, entonces strB
es nulo. Y si strB
es nulo mientras que no es así, entonces definitivamente no son iguales y se puede omitir la función EqualsHelper
.
Eso salvará una llamada de función. Más allá de eso, quizás una mejor comprensión de la función EqualsHelper
pueda arrojar algo de luz sobre por qué se necesita esta optimización.
EDITAR:
Ah, entonces la función EqualsHelper está aceptando a (string, string)
como parámetros. Si strB
es nulo, significa que, o bien era un objeto nulo para empezar, o bien no se pudo convertir con éxito en una cadena. Si el motivo por el que strB
es nulo es que el objeto era de un tipo diferente que no se podría convertir a una cadena, entonces no querría llamar a EqualsHelper con esencialmente dos valores nulos (que devolverán verdadero). La función Equals debería devolver false en este caso. Entonces, si esta declaración es más que una optimización, también asegura una funcionalidad adecuada.