29

我知道||并且&&在 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]);
    }
}

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

那么,do&=|=shortcut,还是IsEven保证在每次迭代时都被调用?在这种情况下,语言规范中是否有任何定义的行为?是否存在这种短路会产生问题的极端情况?

4

3 回答 3

25

C# 规范保证双方从左到右只计算一次,并且不会发生短路。

5.3.3.21 嵌入表达式的一般规则

以下规则适用于这些类型的表达式:带括号的表达式(第 7.6.3 节)、元素访问表达式(第 7.6.6 节)、带索引的基本访问表达式(第 7.6.8 节)、递增和递减表达式(第 7.6.9 节) , §7.7.5), cast 表达式 (§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 复合赋值

...

通过应用二元运算符重载决议(第 7.3.4 节)来处理表单的操作,x op= y就好像该操作是编写的一样x op y。然后,

  • 如果所选运算符的返回类型可隐式转换为 的类型x,则该运算的计算结果为x = x op y,但x只计算一次。

  • 否则,如果所选运算符是预定义运算符,如果所选运算符的返回类型可显式转换为 的类型x,并且如果y可隐式转换xx = (T)(x op y),其中 T 是 的类型x,除了x只计算一次。

...

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


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

在您的示例中,编译器一旦知道结果就可以自由终止循环,因为没有这样的副作用。但这不是必须的。

特别是计时不算作这样的副作用,因此您不能依赖具有恒定运行时间的代码。这在安全环境中可能是有问题的,因为它会引入一个定时边信道。

于 2012-10-24T10:16:58.227 回答
12

但是|=也会&=短路吗?

&=, and|=是操作&and的等价物,而|不是短路逻辑运算符。

于 2012-10-24T09:44:28.097 回答
11

不,&=and|=运算符不进行短路评估。

它们是伪运算符,编译器将其转换为&and|运算符的使用。

这段代码:

allEven &= IsEven(numbers[i]);

完全等同于:

allEven = allEven & IsEven(numbers[i]);

如果要进行短路检查,则必须使用运算符的短路版本将其写出来:

allEven = allEven && IsEven(numbers[i]);

没有&&=伪运算符,但上面的代码正是编译器在有伪运算符时所做的。

于 2012-10-24T09:47:45.497 回答