C#中的| =和&=赋值运算符短路

Pol*_*ial 22 c# short-circuiting

我知道,||&&在C#中被定义为短路运营商,这种行为是由语言规范保证,但做|=&=短路呢?

例如:

private bool IsEven(int n)
{
    return n % 2 == 0;
}

private void Main()
{
    var numbers = new int[] { 2, 4, 6, 8, 9, 10, 14, 16, 17, 18, 20 };

    bool allEven = true;
    bool anyOdd = false;

    for (int i = 0; i < numbers.Length; i++)
    {
        allEven &= IsEven(numbers[i]);
        anyOdd |= !IsEven(numbers[i]);
    }
}
Run Code Online (Sandbox Code Playgroud)

当9条目被命中时,allEven变为false,这意味着所有后续条目都是无关紧要的 - allEven对于将来对该表达式的所有调用,该值保证为false.同样如此anyOdd,当它看到9时设置为true,并且对于该表达式的所有后续调用将保持为true.

那么,做&=|=快捷,或IsEven保证每次迭代都被调用?在这种情况下,语言规范中是否有任何已定义的行为?是否有任何角落情况会导致这种短路问题?

Cod*_*aos 19

C#规范保证双方从左到右精确评估一次,并且不会发生短路.

5.3.3.21具有嵌入式表达式的表达式的一般规则

以下规则适用于这些类型的表达式:带括号的表达式(第7.6.3节),元素访问表达式(第7.6.6节),带索引的基本访问表达式(第7.6.8节),递增和递减表达式(第7.6.9节) ,§7.7.5),强制转换表达式(§7.7.6),一元+, - ,〜,*表达式,二进制+, - ,*,/,%,<<,>>,<,<=,>, > =,==,!=,is,as,&,|,^表达式(§7.8,§7.9,§7.10,§7.11),复合赋值表达式(§7.17.2),检查和未检查表达式(§7.6) .12),加上数组和委托创建表达式(第7.6.10节).

这些表达式中的每一个都具有一个或多个子表达式,这些子表达式以固定顺序无条件地评估.

复合运营商的C#规范说:

7.17.2复合赋值

...

x op= y通过应用二进制运算符重载决策(第7.3.4节)处理表单的操作,就像写入操作一样x op y.然后,

  • 如果所选运算符的返回类型可隐式转换为类型x,则将操作计算为x = x op y,除了x仅计算一次.

  • 否则,如果所选运算符是预定义运算符,如果所选运算符的返回类型可显式转换为类型x,并且if y可隐式转换为类型x或运算符是移位运算符,则操作将被计算为x = (T)(x op y),其中T是类型x,除了x只评估一次.

...

在你的情况下op&|.短路行为反映了&/ |而不是&&/ ||.


请注意,这仅指单线程场景中可见的行为.因此,如果右侧没有在这种情况下可观察到的副作用,编译器或JITter仍然可以省略评估.

在您的示例中,编译器一旦知道结果就可以自由地终止循环,因为没有这样的副作用.但并不要求这样做.

特别是时间并不算作这样的副作用,因此您不能依赖具有恒定运行时的代码.这在安全上下文中可能是有问题的,因为它可以引入定时侧信道.


Kon*_*lph 10

但做|=&=短路呢?

不是.&=并且|=是操作的等同物,&|不是短路的逻辑运算符.

  • -1不,那不对.当用于booelans时,它们不是按位运算符. (5认同)
  • 实际上,这更适合像我这样的其他读者,而不是你,我看到你们都意识到了这一点。 (2认同)

Guf*_*ffa 9

不,&=|=运营商没有进行短路评估.

他们是伪运营商,该编译器转换成利用&|运营商.

这段代码:

allEven &= IsEven(numbers[i]);
Run Code Online (Sandbox Code Playgroud)

完全等同于:

allEven = allEven & IsEven(numbers[i]);
Run Code Online (Sandbox Code Playgroud)

如果您想要短路检查,那么您必须使用运算符的短路版本将其写出:

allEven = allEven && IsEven(numbers[i]);
Run Code Online (Sandbox Code Playgroud)

没有&&=伪运算符,但上面的代码正是编译器如果有的话.


Hen*_*man 5

容易发现:

  bool b = false;
  b &= Foo(1);


    private static bool Foo(int id)
    {
        Console.WriteLine("test " + id);
        return false;
    }
Run Code Online (Sandbox Code Playgroud)

答案是否定的,Foo()总是执行。

但是,如果您正在寻找优化,则真正的问题当然在循环中:

 allEven = numbers.All(n => IsEven(n));
Run Code Online (Sandbox Code Playgroud)

可能会快很多。看到第一个奇数(样本中的9)后,它将停止迭代。