该方法是否在左侧? C#中的运算符被称为两次?一次进行评估,一次进行分配?
在以下行中:
int i = GetNullableInt() ?? default(int);
我认为首先需要调用GetNullableInt()
方法,因此可以在进行赋值之前评估结果。如果这不发生那么变量" i"需要分配然后进行评估,这对接收分配的项目来说似乎是危险的,因为在对象分配期间,理论上可以在第一阶段过早地为其分配空值,只是将其替换为方法的结果。右边。
答案 0 :(得分:11)
有一个bug in the current C# compiler会导致某些方面评估第一个操作数发生两次,在非常特定的情况下 - 但不,GetNullableInt()
只会被调用一次。 (该错误已在罗斯林修复。)
这在第7.13节的C#5规范中有记录,其中选项列表中的每个项目符号(基于所需的转换)包括"在运行时,a
是第一个评价&#34。 (a
是第一个操作数中的表达式。)它只被声明一次,所以它只被评估一次。请注意,只有在需要时才调用第二个操作数(即,如果第一个操作数为null
。)
重要的是,即使i
的类型为int?
,i
的分配也仅在赋值运算符右侧的表达式后发生完全评估。它没有分配一个值,然后可能分配一个不同的值 - 它将确定要分配哪个值,然后分配它。这就是赋值始终的工作原理。当有条件运算符时,这变得非常重要。例如:
Person foo = new Person();
foo = new Person { Spouse = foo };
在将引用分配给Person
之前,这完全控制了新的foo
(将Spouse
的旧值分配给其foo
属性)
答案 1 :(得分:7)
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还是新对象。
答案 2 :(得分:1)
第一个操作数仅计算一次,并且在检查null之前未将结果赋值给变量。
第一个操作数是evalauted,然后检查为null。如果它不为null,则它将成为表达式的值。如果它为null,则计算第二个操作数并将其用作exression的值。之后,将值分配给变量。
好像使用了一个临时变量:
int? temp = GetNullableInt();
if (!temp.HasValue) temp = default(int);
int i = temp;
答案 3 :(得分:0)
我编写了这个简单的控制台应用程序,将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()
(暗示是否有一个值的额外测试),而不是简单地引用{{1}属性,但你有它。当得到JIT时会发生什么,我不知道。
这是MSIL:
Visual Studio 2010 SP1(DEBUG):
Value
Visual Studio 2010 SP1(RELEASE)
.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 2013(DEBUG)
.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(RELEASE)
.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