所有设计过程都会导致相互不兼容的目标之间的妥协。不幸的是,重载的设计过程&&
C++ 中的运算符产生了令人困惑的最终结果:正是您想要的功能&&
-- 其短路行为 -- 被省略。
我不知道该设计过程如何最终落入这个不幸的地方的细节。然而,了解后来的设计过程如何考虑到这一令人不快的结果是相关的。在C#中,重载&&
操作员is短路。 C# 的设计者是如何实现这一点的呢?
其他答案之一建议“lambda 提升”。那是:
A && B
在道德上可以被实现为等同于:
operator_&& ( A, ()=> B )
其中第二个参数使用某种惰性求值机制,以便在求值时产生表达式的副作用和值。重载运算符的实现只会在必要时进行惰性求值。
这不是 C# 设计团队所做的。 (旁白:尽管 lambda 提升is当该做的时候我做了什么表达式树表示 of the ??
运算符,它需要延迟执行某些转换操作。然而,详细描述这一点将是一个重大的题外话。我只想说:lambda 提升有效,但重量级足够大,我们希望避免它。)
相反,C# 解决方案将问题分解为两个独立的问题:
- 我们应该评估右边的操作数吗?
- 如果上面的答案是“是”,那么我们如何组合两个操作数?
因此,通过将超载定为非法来解决问题&&
直接地。相反,在 C# 中你必须重载two运算符,每个运算符都回答这两个问题之一。
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# 要求 if 运算符false
然后提供操作员true
还必须提供,它回答了这个问题:这件事是“真实的吗?”。通常没有理由只提供一个这样的运算符,因此 C# 需要两者都提供。)
考虑以下形式的语句:
C cresult = cleft && cright;
编译器会为此生成代码,就像您编写了这个伪 C# 一样:
C cresult;
C tempLeft = cleft;
cresult = C.false(tempLeft) ? tempLeft : C.&(tempLeft, cright);
正如您所看到的,总是评估左侧。如果确定它是“假的”,那么这就是结果。否则,计算右侧,并且eager用户定义的运算符&
被调用。
The ||
运算符以类似的方式定义,作为运算符 true 和 eager 的调用|
操作员:
cresult = C.true(tempLeft) ? tempLeft : C.|(tempLeft , cright);
通过定义所有四个运算符——true
, false
, &
and |
-- C#让你不仅可以说cleft && cright
而且还没有短路cleft & cright
,并且if (cleft) if (cright) ...
, and c ? consequence : alternative
and while(c)
, 等等。
现在,我说所有的设计过程都是妥协的结果。这里 C# 语言设计者设法短路了&&
and ||
是的,但是这样做需要重载four运算符而不是two,有些人觉得很困惑。运算符 true/false 功能是 C# 中最难理解的功能之一。拥有一种 C++ 用户熟悉的明智且简单的语言的目标遭到了短路的愿望以及不实现 lambda 提升或其他形式的惰性求值的愿望的反对。我认为这是一个合理的妥协立场,但重要的是要认识到它is一种妥协的立场。只是一个不同的比 C++ 设计者所采取的妥协立场。
如果您对此类运算符的语言设计主题感兴趣,请考虑阅读我的系列文章,了解为什么 C# 不在可空布尔值上定义这些运算符:
http://ericlippert.com/2012/03/26/null-is-not-false-part-one/ http://ericlippert.com/2012/03/26/null-is-not-false-part-one/