C#短路求值的最佳实践是什么?

16

在另一个线程中,一篇答案和随后的评论辩论促使我提出以下问题:

在C#中,|| 和 && 是逻辑运算符 | 和 & 的短路版本。

示例用法:

if (String.IsNullOrEmpty(text1) | String.IsNullOrEmpty(text2) | String.IsNullOrEmpty(text3))
{
    //...
}

对比:

if (String.IsNullOrEmpty(text1) || String.IsNullOrEmpty(text2) || String.IsNullOrEmpty(text3))
{
    //...
}

在编码实践中,哪种方法更好用,为什么?

注:我知道这个问题类似于这个问题,但我认为它值得进行特定语言的讨论。

3个回答

45
在编码实践中,哪种方式更好,为什么呢?
简单回答:总是使用短路版本。没有理由不这样做。此外,你可以使代码更清晰,因为你表达了你的意图:逻辑评估。使用位(逻辑)运算符意味着你只想进行位操作,而不是逻辑评估(尽管当应用于布尔值时 MSDN 也将它们称为“逻辑运算符”)。
另外,由于短路只评估需要评估的内容,所以它通常更快,并且它允许编写以下代码:
bool nullorempty = str == null || str.Length == 0;

注意,为了解决这个特定的问题,已经存在一个更好的函数,即string.IsNullOrEmpty,你在问题中也使用了它。如果strnull,即使使用位逻辑运算符,第二个表达式也会被评估,导致NullReferenceException

编辑:如果您希望在逻辑上下文中发生副作用,请仍然不要使用位运算符。这是过于聪明的典型例子。代码的下一个维护者(甚至是您,在几周后)看到这段代码会想“嗯,这段代码可以清理一下,使用条件运算符”,从而不经意间破坏代码。我同情那些负责修复此错误的人。

相反,如果您必须依赖于副作用,请使其显式:

bool hasBuzzed = checkMakeBuzz();
bool isFrobbed = checkMakeFrob();
bool result = hasBuzzed || isFrobbed;

虽然需要三行代码而不是一行,但结果是更清晰的代码。


1
这是一个很好的回答,但你能否将“位运算”操作符更改为“逻辑”并将“逻辑”更改为“条件”?这是MSDN使用的术语。从技术上讲,两者都是“逻辑”,但双重版本也具有短路功能。 - Jimmy
4
完美的答案。+1 对副作用讨论的支持 - 一直保持明确性以便维护。3 行代码与维护噩梦相比,3 行代码轻松胜出。 - John Rudy
2
即使您不认为SC评估是正确的方法,也请这样做,因为人们已经以这种方式编写C/C++代码30年了。现在它几乎成为共识法则。 - Dave Markle
@Hans 我非常坚信这需要在编译器中进行修复,而不是在用户代码中。因为在用户代码中修复会导致(a)削弱代码的保证,以及(b)执行无法预测且有时无法基准测试的“优化”,从而(c)过早的优化,这通常会变成(d)悲观的结果。简而言之,这不是用户代码优化的好策略。这是一个优化编译器的工作。 - Konrad Rudolph
哦,我从未见过一个程序因为强烈的观点而被优化。事实上,这是最糟糕的做法。 - Hans Passant
显示剩余4条评论

7

我将倒着回答这个问题:什么时候我唯一会使用逻辑运算符?

当我有一系列必须全部满足的(低成本)条件时,我有时会使用逻辑比较。例如:

bool isPasswordValid = true;

isPasswordValid &= isEightCharacters(password);
isPasswordValid &= containsNumeric(password);
isPasswordValid &= containsBothUppercaseAndLowercase(password);

return isPasswordValid;

在我看来,上述内容比以下内容更易读:
return (isEightCharacters(password) &&
        containsNumberic(password)  &&
        containsBothUppercaseAndLowercase(password));

缺点是它有点玄学。

啊,位运算符赋值的使用很有趣。谢谢! - Michael Whatcott
2
严格按照您的示例,如果密码是7个字符,则无效。因此,它是否包含数字和/或大写/小写字母都无关紧要,也不会改变任何内容。确实应该应用短路计算。如果单个返回语句的可读性成问题,您可以将代码更改为三行,即:if (!RequirementCheck(input)) return false; 然后最后一行只需说 return true;。但是,如果您想让每个要求检查返回特定消息,然后组合这些消息,那么此示例不再适用于此问题,我们有更多的无关信息。 - Suamere
有时我会在循环内这样使用位运算符赋值操作符,例如: int sum = 0; bool anyOdd = false; foreach (int number in GetNumbers()) { sum += number; anyOdd |= number % 2 == 1; }但是在这个答案中,我同意@Suamere的观点,短路更加简洁。 - hypehuman

2

当您只关心结果并希望尽快了解结果时,使用&&||,并且您的表达式没有副作用,即使布尔条件未满足也不必发生。也就是说,几乎总是这样。

当每个表达式都必须被评估时(例如,如果您的表达式具有副作用),请使用&|。但由于您的程序不应该依赖于必须在布尔条件未满足时发生的副作用,因此您可能不应该使用&|

例如,这可能会异常愚蠢:

if (false & somethingThatUpdatesTheDatabase()) { /* ... */ }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接