Null coalesce operator implications?

Некоторое время назад я скомпилировал две версии кода, одну с использованием (Nullable)x.GetValueOrDefault(y) и другую с использованием (Nullable)x ?? y).

После декомпиляции в IL я заметил, что оператор null coalesce преобразуется в вызов GetValueOrDefault.

Поскольку это вызов метода, в который можно передать выражение, которое оценивается перед выполнением метода, y, похоже, выполняется всегда.

Например:

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();
    }
}

Кажется, что это приведет к неработоспособному объекту, и, возможно, к последствиям для производительности.

Прежде всего, так ли это? Или JIT или что-то подобное изменяет фактически выполняемый ASM-код?

И во-вторых, может ли кто-нибудь объяснить, почему это имеет такое поведение?

ПРИМЕЧАНИЕ: Это просто пример, он не основан на реальном коде, и, пожалуйста, воздержитесь от комментариев типа "это плохой код".

IL DASM:
Хорошо, когда я скомпилировал это с .Net Framework 2.0, получился идентичный код с вызовом null coalesce и GetValueOrDefault. В .Net Framework 4.0 он генерирует эти два кода:

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

Null 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

Как оказалось, это уже не так, и что он вообще пропускает вызов GetValueOrDefault, когда HasValue возвращает false.

8
задан Aidiakapi 8 January 2012 в 20:34
поделиться