运营商的短路行为&&
和 ||
对于程序员来说是一个了不起的工具。
但是为什么它们在过载时会失去这种行为呢?我知道运算符只是函数的语法糖,而是 bool
的运算符。有这种行为,为什么要仅限于这种单一类型?这背后有什么技术原因吗?
最佳答案
所有的设计过程都会导致相互不兼容的目标之间的妥协。可惜设计过程为重载&&
C++ 中的运算符产生了一个令人困惑的最终结果:正是您想要的特性 &&
-- 它的短路行为 -- 被省略了。
设计过程如何在这个不幸的地方结束的细节,我不知道。然而,看看后来的设计过程如何考虑这种令人不快的结果是很重要的。在 C# 中,重载 &&
运算符(operator)短路。 C# 的设计者是如何做到这一点的?
其他答案之一建议“lambda 提升”。即:
A && B
可以实现为在道德上等同于:
operator_&& ( A, ()=> B )
其中第二个参数使用某种惰性求值机制,以便在求值时产生表达式的副作用和值。重载运算符的实现只会在必要时进行惰性求值。
这不是 C# 设计团队所做的。 (旁白:虽然 lambda 提升是我在做
??
运算符的表达式树表示时所做的,这需要延迟执行某些转换操作。然而,详细描述这将是一个主要的题外话。我只想说:lambda 提升有效,但重量足够大,我们希望避免它。)相反,C# 解决方案将问题分解为两个单独的问题:
因此,通过使重载
&&
成为非法来解决问题。直接。相反,在 C# 中,您必须重载两个运算符,每个运算符都回答这两个问题之一。class C
{
// Is this thing "false-ish"? If yes, we can skip computing the right
// hand size of an &&
public static bool operator false (C c) { whatever }
// If we didn't skip the RHS, how do we combine them?
public static C operator & (C left, C right) { whatever }
...
(旁白:实际上,三个。C# 要求,如果提供了运算符
false
,则还必须提供运算符 true
,这回答了这个问题:这个东西是“真实的吗?”。通常没有理由提供只有一个这样的运算符,所以 C# 需要两者。)考虑以下形式的声明:
C cresult = cleft && cright;
编译器为此生成代码,因为您认为您编写了这个伪 C#:
C cresult;
C tempLeft = cleft;
cresult = C.false(tempLeft) ? tempLeft : C.&(tempLeft, cright);
如您所见,始终评估左侧。如果它被确定为“false-ish”,那么它就是结果。否则,评估右侧,并使用急切的用户定义运算符
&
被调用。||
运算符以类似的方式定义,作为对运算符 true 和急切 |
的调用。运营商:cresult = C.true(tempLeft) ? tempLeft : C.|(tempLeft , cright);
通过定义所有四个运算符 --
true
, false
, &
和 |
-- C#让你不仅可以说cleft && cright
但也非短路cleft & cright
,还有 if (cleft) if (cright) ...
, 和 c ? consequence : alternative
和 while(c)
,等等。现在,我说所有的设计过程都是妥协的结果。在这里,C# 语言设计者设法实现了短路
&&
和 ||
是的,但这样做需要重载四个运算符而不是两个,有些人会觉得这令人困惑。运算符 true/false 功能是 C# 中最不为人所知的功能之一。拥有一种为 C++ 用户所熟悉的明智而直接的语言的目标与短路的愿望和不实现 lambda 提升或其他形式的惰性求值的愿望相反。我认为这是一个合理的妥协立场,但重要的是要意识到这是一个妥协立场。只是与 C++ 设计者所采取的妥协立场不同。如果您对此类运算符的语言设计主题感兴趣,请考虑阅读我关于为什么 C# 不在可空 bool 值上定义这些运算符的系列:
http://ericlippert.com/2012/03/26/null-is-not-false-part-one/
关于c++ - 重载 && 和 || 是否真的有原因?不要短路?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25913237/