C#??操作员被叫两次?

时间:2014-06-18 19:42:49

标签: c#

该方法是否在左侧? C#中的运算符被称为两次?一次进行评估,一次进行分配?

在以下行中:

int i = GetNullableInt() ?? default(int);

我认为首先需要调用GetNullableInt()方法,因此可以在进行赋值之前评估结果。如果这不发生那么变量" i"需要分配然后进行评估,这对接收分配的项目来说似乎是危险的,因为在对象分配期间,理论上可以在第一阶段过早地为其分配空值,只是将其替换为方法的结果。右边。

?? Operator (C# Reference)

4 个答案:

答案 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 ;

对我来说,生成的代码似乎有点奇怪

  1. 首先检查一个值,然后
  2. 事先知道int?确实有一个值,调用GetValueOrDefault()(暗示是否有一个值的额外测试),而不是简单地引用{{1}属性,但你有它。
  3. 当得到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