C#??


Does C# ?? operator get called twice?

方法是否位于的左侧??C中的操作员接到两次电话?一次用于评估,一次用于分配?

在以下行中:

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

我假设需要首先调用GetNullableInt()方法,以便在进行分配之前评估结果。如果没有发生这种情况,那么就需要分配变量"i",然后对接收到分配的项目进行评估,因为在对象分配过程中,理论上可以在第一阶段过早地分配一个空值,只需将其替换为右侧方法的结果。

??操作员(C参考)


当前C编译器中存在一个错误,它将导致计算第一个操作数的某些方面出现两次,在非常特定的情况下-但不是,GetNullableInt()只会被调用一次。(而且这个bug已经在罗斯林被修复了。)

这在第7.13节的C 5规范中有记录,其中选项列表中的每个项目符号(基于需要的转换)包括"在运行时,首先对a进行计算。"(a是第一个操作数中的表达式。)它只声明一次,因此只计算一次。注意,第二个操作数只有在需要时才被调用(即,如果第一个操作数是null)。

重要的是,即使i的类型是int?的类型,对i的赋值也只在赋值运算符右边的表达式被完全计算之后发生。它不分配一个值,然后可能分配一个不同的值——它计算出要分配哪个值,然后再分配它。这就是作业的工作方式。当有条件运算符时,这变得非常重要。例如:

1
2
Person foo = new Person();
foo = new Person { Spouse = foo };

这完全建立了新的Person(将foo的旧值分配给其Spouse属性),然后再将引用分配给foo


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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返回空值还是新对象。


第一个操作数只计算一次,在检查空值之前,不会将结果赋给变量。

第一个操作数被评估,然后检查是否为空。如果不为空,则它将成为表达式的值。如果为空,则计算第二个操作数并将其用作表达式的值。之后,将值赋给变量。

就好像使用了一个临时变量:

1
2
3
int? temp = GetNullableInt();
if (!temp.HasValue) temp = default(int);
int i = temp;

我编写了这个简单的控制台应用程序,将GetNullableInt()方法放在一个外部程序集中,以简化以下内容:

1
2
3
4
5
static int Main( string[] args )
{
  int i = SomeHelpers.GetNullableInt() ?? default(int) ;
  return i ;
}

这是以不同方式生成的IL。您将注意到,在所有情况下,GetNullableInt()只被调用一次,至少在通常情况下是这样(不能与可能调用编译器bug的奇怪边缘条件对话)。似乎代码

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

大致相当于

1
2
int? t = GetNullableInt() ;
int  i = t.HasValue ? t.GetValueOrDefault() : 0 ;

在我看来,生成的代码

  • 首先检查一个值,然后,
  • 事先知道int?实际上有一个值,称之为GetValueOrDefault()(意味着对它是否有一个值的附加测试),而不是简单地引用Value属性,但你已经有了它。
  • 我不知道,当这种情况发生时会发生什么。

    这是MSIL:

    • Visual Studio 2010 SP1(调试):

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      .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(版本)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      .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(调试)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      .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(版本)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      .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