Null合并运算符的含义?

Aid*_*api 8 c# nullable

前一阵子我编译了两个版本的代码,一个使用(Nullable<T>)x.GetValueOrDefault(y),一个使用(Nullable<T>)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();
    }
}
Run Code Online (Sandbox Code Playgroud)

似乎导致一个不利的对象,也可能是性能影响.

首先,这是真的吗?或者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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

事实证明这不再是这种情况,并且当它返回false GetValueOrDefault时它会完全跳过调用HasValue.

小智 6

在反编译为IL之后,我注意到null coalesce运算符被转换为GetValueOrDefault调用.

x ?? y变成了x.HasValue ? x.GetValueOrDefault() : y.它没有转化为x.GetValueOrDefault(y),如果它是一个编译器错误.你是对的,y不应该被评估,如果x不是null,它不是.

编辑:如果评估y可以证明没有副作用(其中"副作用"包括"抛出异常"),那么转换x.GetValueOrDefault(y)不一定是错误的,但它仍然是一个转变,我不认为编译器执行:并不是所有那些优化有用的情况.