2011-04-05 11 views
9

Me enseño a mí mismo C# (todavía no sé mucho). En este sencillo ejemplo:¿por qué funciona n.GetHashCode() pero n.GetType() arroja y la excepción?

bool?   n = null; 

Console.WriteLine("n    = {0}", n); 
Console.WriteLine("n.ToString() = {0}", n.ToString()); 
Console.WriteLine("n.GetHashCode() = {0}", n.GetHashCode()); 

// this next statement causes a run time exception 

Console.WriteLine("n.GetType()  = {0}", n.GetType()); 

Intuitivamente entiendo por qué el método GetType() arrojaría una excepción. La instancia n es nula, lo que explicaría eso, pero ¿por qué no obtengo una excepción por el mismo motivo cuando uso n.GetHashCode() y ToString()?

Gracias por su ayuda,

Juan.

+5

mira esto: http://stackoverflow.com/questions/194484/whats-the-strangest-corner-case-youve-seen-in-c-or-net/194671#194671 – mookid8000

+0

mookid, gran hilo. Gracias por señalarlo. – Hex440bx

Respuesta

14

GetHashCode() es un método virtual se reemplaza en Nullable<T>: cuando se llama a un valor Nullable<T>, se utiliza la aplicación Nullable<T>, sin ningún tipo de boxeo.

GetType() no es un método virtual, lo que significa que cuando se llama, el valor se encajona primera ... y boxeo un resultado "nulo" anulables de valor en una referencia nula - de ahí la excepción. Podemos ver esto desde el IL:

static void Main() 
{ 
    bool? x = null; 
    Type t = x.GetType(); 
} 

se compila a:

.method private hidebysig static void Main() cil managed 
{ 
    .entrypoint 
    .maxstack 1 
    .locals init (
     [0] valuetype [mscorlib]System.Nullable`1<bool> nullable, 
     [1] class [mscorlib]System.Type 'type') 
    L_0000: nop 
    L_0001: ldloca.s nullable 
    L_0003: initobj [mscorlib]System.Nullable`1<bool> 
    L_0009: ldloc.0 
    L_000a: box [mscorlib]System.Nullable`1<bool> 
    L_000f: callvirt instance class [mscorlib]System.Type [mscorlib]System.Object::GetType() 
    L_0014: stloc.1 
    L_0015: ret 
} 

El bit importante aquí es L_000a: box la instrucción antes de la instrucción callvirt en L_000f.

Ahora compare eso con el código equivalente llamando GetHashCode:

static void Main() 
{ 
    bool? x = null; 
    int hash = x.GetHashCode(); 
} 

compila a:

.method private hidebysig static void Main() cil managed 
{ 
    .entrypoint 
    .maxstack 1 
    .locals init (
     [0] valuetype [mscorlib]System.Nullable`1<bool> nullable, 
     [1] int32 num) 
    L_0000: nop 
    L_0001: ldloca.s nullable 
    L_0003: initobj [mscorlib]System.Nullable`1<bool> 
    L_0009: ldloca.s nullable 
    L_000b: constrained [mscorlib]System.Nullable`1<bool> 
    L_0011: callvirt instance int32 [mscorlib]System.Object::GetHashCode() 
    L_0016: stloc.1 
    L_0017: ret 
} 

Esta vez tenemos un constrained instrucción/prefijo antes callvirt, lo que significa esencialmente "Usted don' Necesito boxear cuando llamas al método virtual ". Desde el OpCodes.Constrained documentación:

El prefijo restringido está diseñado para permitir a las instrucciones callvirt hacerse de una manera uniforme, independiente de si thisType es un tipo de valor o de un tipo de referencia.

(Siga el enlace para más información.)

Tenga en cuenta que la forma de boxeo de los tipos de valor anulables trabajo también significa que incluso para un no nulo valor, no se va a Nullable<T>. Por ejemplo, considere:

int? x = 10; 
Type t = x.GetType(); 
Console.WriteLine(t == typeof(int?)); // Prints False 
Console.WriteLine(t == typeof(int)); // Prints True 

Así que el tipo de levantarse es el tipo no anulable involucrados. Una llamada a object.GetType() será nunca devolverá un tipo Nullable<T>.

+0

respuesta corta y sólida. –

+0

Jon !! muchas gracias.Empecé a estudiar C# usando tu libro "C# in Depth" pero rápidamente se me pasó por la cabeza. Como resultado, fui a la "Guía de programación C#" de VS2008. Tu explicación tiene mucho sentido. Gracias de nuevo, John – Hex440bx

+0

@Tomas: Estoy volviendo más largo en el tiempo, me temo, pero me gusta entrar en detalles :) –

Cuestiones relacionadas