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.
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. –
@ 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
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. –