前阵子我编译了两个版本的代码,一个使用(Nullable<T>)x.GetValueOrDefault(y)
,一个使用(Nullable<T>)x ?? y)
。
反编译为IL后,我注意到将空合并运算符转换为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合并和GetValueOrDefault得到了相同的代码。使用.Net Framework 4.0,它将生成以下两个代码:
GetValue或默认值:
.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
空合并:
.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
返回false时,它将完全跳过HasValue
调用。 最佳答案
x ?? y
转换为x.HasValue ? x.GetValueOrDefault() : y
。它不会转换为x.GetValueOrDefault(y)
,如果是的话,则可能是编译器错误。没错,如果y
不为null,那么就不应该评估x
。
编辑:如果可以证明对y
的评估没有副作用(其中“副作用”包括“引发异常”),那么对x.GetValueOrDefault(y)
的转换不一定是错误的,但这仍然是我没有的转换认为编译器会执行:没有很多情况下进行优化会很有用。
关于c# - 空合并运算符的含义是什么?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8780597/