c# ??接线员被叫了两次
本文关键字:两次 接线员 | 更新日期: 2023-09-27 18:09:08
方法的左侧??操作符在c#中被调用两次?一次是评估,一次是作业?
下一行:
int i = GetNullableInt() ?? default(int);
我假设需要首先调用GetNullableInt()
方法,以便在进行赋值之前对结果进行评估。如果没有发生这种情况,则需要对变量"i"进行赋值,然后求值,这对于接受赋值的项来说似乎是危险的,因为在对象赋值期间,理论上可以在第一阶段过早地将其赋值为空值,然后将其替换为右侧方法的结果。
? ?操作符(c#参考)
当前的c#编译器中有一个bug,在非常特殊的情况下,会导致第一个操作数求值的某些方面出现两次,但是不会,GetNullableInt()
只会被调用一次。(Roslyn已经修复了这个bug)
这在c# 5规范第7.13节中有记录,其中选项列表中的每个项目(基于需要的转换)都包括"在运行时,首先求值a
"。(a
是第一个操作数中的表达式。)它只被表述一次,所以只被求值一次。注意,第二个操作数只有在需要时才会被调用(即,如果第一个操作数是null
)
i
的类型是int?
,对i
的赋值也只有在赋值操作符右侧的表达式完全求值之后才会发生。它不会先赋一个值,然后再赋一个不同的值——它会计算出要赋的值,然后赋给它。这就是赋值总是的工作原理。当有条件运算符时,这就变得非常重要了。例如:Person foo = new Person();
foo = new Person { Spouse = foo };
在将引用赋值给foo
之前,完全构建了新的Person
(将foo
的旧值赋值给它的Spouse
属性)。
namespace ConsoleApplication
{
class Test
{
private static int count = 0;
public static object TestMethod()
{
count++;
return null;
}
}
class Program
{
static void Main(string[] args)
{
var test = Test.TestMethod() ?? new object();
}
}
}
我刚刚编写了这个测试应用程序。在运行Test.TestMethod()之后,看起来它只增加了一次,所以看起来它只被调用了一次,不管TestMethod返回的是null还是一个新对象。
第一个操作数只计算一次,并且在检查null之前不将结果赋值给变量。
计算第一个操作数,然后检查是否为空。如果它不为空,它就成为表达式的值。如果它为空,则计算第二个操作数并将其用作表达式的值。然后将值赋给变量。
就好像使用了一个临时变量:
int? temp = GetNullableInt();
if (!temp.HasValue) temp = default(int);
int i = temp;
我编写了这个简单的控制台应用程序,将GetNullableInt()
方法放在外部程序集中以简化事情:
static int Main( string[] args )
{
int i = SomeHelpers.GetNullableInt() ?? default(int) ;
return i ;
}
这是通过不同方式生成的IL。你会注意到GetNullableInt()
在所有情况下只被调用一次…至少在通常情况下是这样(不能使用可能调用编译器错误的古怪边缘条件)。看起来代码
int i = GetNullableInt() ?? default(int) ;
大致相当于
int? t = GetNullableInt() ;
int i = t.HasValue ? t.GetValueOrDefault() : 0 ;
对我来说似乎有点奇怪,生成的代码
- 首先检查一个值,然后,
- 提前知道
int?
实际上有一个值,调用GetValueOrDefault()
(意味着它是否有一个值的额外测试),而不是简单地引用Value
属性,但是你有它。
我不知道当它被JIT时会发生什么。
下面是MSIL:
Visual Studio 2010 SP1 (DEBUG):
.method private hidebysig static int32 Main(string[] args) cil managed { .entrypoint // Code size 33 (0x21) .maxstack 2 .locals init ([0] int32 i, [1] int32 CS$1$0000, [2] valuetype [mscorlib]System.Nullable`1<int32> CS$0$0001) IL_0000: nop IL_0001: call valuetype [mscorlib]System.Nullable`1<int32> [SomeLibrary]SomeLibrary.SomeHelpers::GetNullableInt() IL_0006: stloc.2 IL_0007: ldloca.s CS$0$0001 IL_0009: call instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue() IL_000e: brtrue.s IL_0013 IL_0010: ldc.i4.0 IL_0011: br.s IL_001a IL_0013: ldloca.s CS$0$0001 IL_0015: call instance !0 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault() IL_001a: stloc.0 IL_001b: ldloc.0 IL_001c: stloc.1 IL_001d: br.s IL_001f IL_001f: ldloc.1 IL_0020: ret } // end of method Program::Main
Visual Studio 2010 SP1 (RELEASE)
.method private hidebysig static int32 Main(string[] args) cil managed { .entrypoint // Code size 28 (0x1c) .maxstack 2 .locals init ([0] int32 i, [1] valuetype [mscorlib]System.Nullable`1<int32> CS$0$0000) IL_0000: call valuetype [mscorlib]System.Nullable`1<int32> SomeLibrary]SomeLibrary.SomeHelpers::GetNullableInt() IL_0005: stloc.1 IL_0006: ldloca.s CS$0$0000 IL_0008: call instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue() IL_000d: brtrue.s IL_0012 IL_000f: ldc.i4.0 IL_0010: br.s IL_0019 IL_0012: ldloca.s CS$0$0000 IL_0014: call instance !0 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault() IL_0019: stloc.0 IL_001a: ldloc.0 IL_001b: ret } // end of method Program::Main
Visual Studio 2013 (DEBUG)
.method private hidebysig static int32 Main(string[] args) cil managed { .entrypoint // Code size 34 (0x22) .maxstack 1 .locals init ([0] int32 i, [1] int32 CS$1$0000, [2] valuetype [mscorlib]System.Nullable`1<int32> CS$0$0001) IL_0000: nop IL_0001: call valuetype [mscorlib]System.Nullable`1<int32> [SomeLibrary]SomeLibrary.SomeHelpers::GetNullableInt() IL_0006: stloc.2 IL_0007: ldloca.s CS$0$0001 IL_0009: call instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue() IL_000e: brtrue.s IL_0013 IL_0010: ldc.i4.0 IL_0011: br.s IL_001a IL_0013: ldloca.s CS$0$0001 IL_0015: call instance !0 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault() IL_001a: nop IL_001b: stloc.0 IL_001c: ldloc.0 IL_001d: stloc.1 IL_001e: br.s IL_0020 IL_0020: ldloc.1 IL_0021: ret } // end of method Program::Main
Visual Studio 2013 (RELEASE)
.method private hidebysig static int32 Main(string[] args) cil managed { .entrypoint // Code size 28 (0x1c) .maxstack 1 .locals init ([0] int32 i, [1] valuetype [mscorlib]System.Nullable`1<int32> CS$0$0000) IL_0000: call valuetype [mscorlib]System.Nullable`1<int32> [SomeLibrary]SomeLibrary.SomeHelpers::GetNullableInt() IL_0005: stloc.1 IL_0006: ldloca.s CS$0$0000 IL_0008: call instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue() IL_000d: brtrue.s IL_0012 IL_000f: ldc.i4.0 IL_0010: br.s IL_0019 IL_0012: ldloca.s CS$0$0000 IL_0014: call instance !0 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault() IL_0019: stloc.0 IL_001a: ldloc.0 IL_001b: ret } // end of method Program::Main