c# ??接线员被叫了两次

本文关键字:两次 接线员 | 更新日期: 2023-09-27 18:09:08

方法的左侧??操作符在c#中被调用两次?一次是评估,一次是作业?

下一行:

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

我假设需要首先调用GetNullableInt()方法,以便在进行赋值之前对结果进行评估。如果没有发生这种情况,则需要对变量"i"进行赋值,然后求值,这对于接受赋值的项来说似乎是危险的,因为在对象赋值期间,理论上可以在第一阶段过早地将其赋值为空值,然后将其替换为右侧方法的结果。

? ?操作符(c#参考)

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 ;

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

  1. 首先检查一个值,然后,
  2. 提前知道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