2012-01-08 9 views
8

Hace un tiempo compilé dos versiones de un código, una usando (Nullable<T>)x.GetValueOrDefault(y) y otra usando (Nullable<T>)x ?? y).Implicaciones del operador nulo coalesce?

Después de descompilar a IL noté que el operador de fusión nula se transforma en la llamada GetValueOrDefault.

Dado que es una llamada de método a la que se puede pasar una expresión que se evalúa antes de la ejecución del método, y parece que siempre se ejecuta.

Por ejemplo:

using System; 

public static class TestClass 
{ 
    private class SomeDisposable : IDisposable 
    { 
     public SomeDisposable() 
     { 
      // Allocate some native resources 
     } 

     private void finalize() 
     { 
      // Free those resources 
     } 

     ~SomeDisposable() 
     { 
      finalize(); 
     } 

     public void Dispose() 
     { 
      finalize(); 
      GC.SuppressFinalize(this); 
     } 
    } 

    private struct TestStruct 
    { 
     public readonly SomeDisposable _someDisposable; 
     private readonly int _weirdNumber; 

     public TestStruct(int weirdNumber) 
     { 
      _weirdNumber = weirdNumber; 
      _someDisposable = new SomeDisposable(); 
     } 
    } 

    public static void Main() 
    { 
     TestStruct? local = new TestStruct(0); 

     TestStruct local2 = local ?? new TestStruct(1); 

     local2._someDisposable.Dispose(); 
    } 
} 

parece resultar en un objeto indispuesto, y probablemente consecuencias en el rendimiento también.

Antes que nada, ¿es esto cierto? ¿O el JIT o algo similar cambia el código ASM realmente ejecutado?

Y en segundo lugar, ¿alguien puede explicar por qué tiene este comportamiento?

NOTA: Esto es solo un ejemplo, no se basa en código real, y por favor absténgase de hacer comentarios como "este es un código incorrecto".

IL DASM:
bien, cuando Compilé esto con .Net Framework 2,0 resultó en código idénticos con llamar coalesce nula y GetValueOrDefault. Con .Net Framework 4.0, que genera estos dos códigos:

GetValueOrDefault:

.method private hidebysig static void Main() cil managed 
{ 
    .entrypoint 
    // Code size  19 (0x13) 
    .maxstack 2 
    .locals init ([0] valuetype [mscorlib]System.Nullable`1<int32> nullableInt, 
      [1] int32 nonNullableInt) 
    IL_0000: nop 
    IL_0001: ldloca.s nullableInt 
    IL_0003: initobj valuetype [mscorlib]System.Nullable`1<int32> 
    IL_0009: ldloca.s nullableInt 
    IL_000b: ldc.i4.1 
    IL_000c: call  instance !0 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault(!0) 
    IL_0011: stloc.1 
    IL_0012: ret 
} // end of method Program::Main 

nulo Coalesce:

.method private hidebysig static void Main() cil managed 
{ 
    .entrypoint 
    // Code size  32 (0x20) 
    .maxstack 2 
    .locals init (valuetype [mscorlib]System.Nullable`1<int32> V_0, 
      int32 V_1, 
      valuetype [mscorlib]System.Nullable`1<int32> V_2) 
    IL_0000: nop 
    IL_0001: ldloca.s V_0 
    IL_0003: initobj valuetype [mscorlib]System.Nullable`1<int32> 
    IL_0009: ldloc.0 
    IL_000a: stloc.2 
    IL_000b: ldloca.s V_2 
    IL_000d: call  instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue() 
    IL_0012: brtrue.s IL_0017 
    IL_0014: ldc.i4.1 
    IL_0015: br.s  IL_001e 
    IL_0017: ldloca.s V_2 
    IL_0019: call  instance !0 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault() 
    IL_001e: stloc.1 
    IL_001f: ret 
} // end of method Program::Main 

Como resulta que esto ya no es el caso , y que omite la llamada GetValueOrDefault cuando HasValue devuelve falso.

+0

Si 'y' es una llamada a un método que da como resultado un objeto que debe eliminarse, entonces hay una fuga allí en cualquier caso que' x' sea nulo. –

+0

@ M.Babcock No es realmente una fuga, solo una posposición de aclarar la memoria.Y mire el ejemplo revisado, espero que esto explique mejor el problema. – Aidiakapi

+0

En realidad dije eso mal, si 'local' no es nulo y el método es realmente llamado, entonces usted tendría una fuga porque el resultado se obtendría del GC sin desecharlo. Para responder a la pregunta de si realmente se llama al método cada vez, puede poner un punto de interrupción en el método y ejecutarlo. –

Respuesta

6

Después de decompilar a IL, noté que el operador de fusión nula se transforma en la llamada GetValueOrDefault.

x ?? y se transforma en x.HasValue ? x.GetValueOrDefault() : y. No se transforma en x.GetValueOrDefault(y), y sería un error de compilación si fuera así. Tiene razón, y no debe evaluarse si x no es nulo, y no lo es.

Edición: Si la evaluación de y puede probarse libre de efectos secundarios (donde "efecto secundario" incluye "una excepción"), a continuación, una transformación a x.GetValueOrDefault(y) no sería necesariamente malo, pero sigue siendo una transformación que no creo que el compilador realice: no hay tantas situaciones en las que esa optimización sería útil.

+0

Realicé esta prueba hace mucho tiempo utilizando el compilador .Net Framework 2.0, también hice algunos recursos más, y desde el código de referencia suministra por MS, está claro que 'GetValueOrDefault' devuelve el valor interno directamente, mientras que' get_Value' realiza un primero verifique, es más probable por qué elige 'GetValueOrDefault' en lugar de' get_Value'. Gracias :) – Aidiakapi

+0

Sí, el getter de propiedad 'Value' es básicamente' if (! HasValue) throw; return GetValueOrDefault(); '- así que no tiene mucho sentido llamarlo si ya ha determinado si el objeto que admite nulos tiene un valor :) – hvd

+0

O para ser exactos' if (! HasValue) throw' ... '; valor de retorno; '. Mientras 'GetValueOrDefault()' es 'return value;'. Muy interesante que es válido debido a la inmutabilidad de (la mayoría) de las estructuras, simplemente tiene que recrear el contenedor 'Nullable ' alrededor de 'T' así que no hay mucha utilidad para verificar HasValue en' GetValueOrDefault() 'ya que la estructura comienza automáticamente out predeterminado a todos los bits 0. – Aidiakapi

Cuestiones relacionadas