【问题标题】:Does C# ?? operator get called twice? [closed]有 C# 吗?操作员被调用两次? [关闭]
【发布时间】:2014-08-09 05:41:15
【问题描述】:

左边的方法是?? C# 中的运算符被调用两次?一次用于评估,一次用于分配?

在下面一行:

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

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

?? Operator (C# Reference)

【问题讨论】:

  • 为什么假设它会被调用两次? - 如果是,那么如果方法返回不同的值(它有权获得),结果可能是错误的
  • 好像可以测试一下。
  • 我不这么认为。它可能调用该方法,将返回值分配给i,检查i 的值是否为null,如果是,则分配正确的部分。我就是这样做的.....
  • @MarioStoilov 它将使用中间变量,但不一定是i。虽然运行时有很多选项,但通常这(以及实际上相当多的操作)会导致隐式未命名的临时变量。毕竟,考虑一下如果表达式本身在其中使用i 会发生什么
  • @MarioStoilov:不,这不是发生的事情,而且它不可能是发生的事情 - 因为i 永远不能为空。空合并表达式被完全评估,结果分配给i

标签: c#


【解决方案1】:

有一个bug in the current C# compiler 会导致评估第一个操作数的某些方面出现两次,在非常特定的情况下 - 但不,GetNullableInt() 只会被调用一次。 (并且该错误已在 Roslyn 中修复。)

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

重要的是,即使i 的类型是int?,对i 的赋值也只会发生在赋值运算符右侧的表达式被完全求值之后。它不会分配一个值,然后可能会分配一个不同的值 - 它会确定要分配哪个值,然后分配它。这就是分配总是的工作原理。当有条件运算符时,这变得非常重要。例如:

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

这完全构建了新的Person(将foo值分配给它的Spouse 属性),然后再分配对foo 的引用。

【讨论】:

  • 顺便说一句,?? 不只是 NUllable<T>.GetValueOrDefault 的语法糖吗?
  • @BradChristie:不,一点也不。一方面,除非第一个操作数为 null,否则不会计算第二个操作数,并且它也适用于引用类型。
【解决方案2】:
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还是新对象。

【讨论】:

    【解决方案3】:

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

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

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

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

    【讨论】:

      【解决方案4】:

      我编写了这个简单的控制台应用程序,将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(调试):

        .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(发布)

        .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(调试)

        .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(发布)

        .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
        

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-30
        • 1970-01-01
        • 2011-07-16
        • 1970-01-01
        • 2022-11-01
        • 2022-10-13
        相关资源
        最近更新 更多