C# 是不是使用 await 对 if 语句执行短路评估?
Posted
技术标签:
【中文标题】C# 是不是使用 await 对 if 语句执行短路评估?【英文标题】:Does C# perform short circuit evaluation of if statements with await?C# 是否使用 await 对 if 语句执行短路评估? 【发布时间】:2020-12-28 17:48:32 【问题描述】:我相信一旦 C# 能够判断结果,它就会停止评估 if 语句条件。比如:
if ( (1 < 0) && check_something_else() )
// this will not be called
由于条件(1 < 0)
的计算结果为false
,因此无法满足&&
条件,并且不会调用check_something_else()
。
C# 如何评估带有异步函数的 if 语句?是不是要等两个人都回来?比如:
if( await first_check() && await second_check() )
// ???
这会短路吗?
【问题讨论】:
if
和 await
都不影响短路。 async
不会影响语言的行为,除了允许使用 await
,等待已经执行的异步操作而不会阻塞。
Operator &&
文档的哪一部分让您认为它可以永远跳过短路?
@IanKemp:我认为你需要重新阅读 Alexei 所说的话......
但是,短路和布尔逻辑是不一样的。 ( && false) 也只能计算为 false,但 C、C#、C++ 已决定短路仅基于第一个参数。
短路与if
无关。 &&
和 ||
无论在哪里使用都会执行短路,例如some_var = <expression1> && <expression2>
【参考方案1】:
是的,它会短路。您的代码相当于:
bool first = await first_check();
if (first)
bool second = await second_check();
if (second)
...
注意它甚至不会调用 second_check
,直到first_check
返回的等待完成。所以请注意,这不会并行执行两个检查。如果你想这样做,你可以使用:
var t1 = first_check();
var t2 = second_check();
if (await t1 && await t2)
那时:
这两个检查将并行执行(假设它们是真正异步的) 它将等待第一次检查完成,然后只等待第二次检查完成如果第一次返回true 如果第一次检查返回 false,但第二次检查失败并出现异常,则该异常将被有效地吞噬 如果第二次检查返回 false 真的很快,但第一次检查需要很长时间,那么整体操作将需要很长时间,因为它首先等待第一次检查完成如果你想并行执行检查,一旦任何返回false就结束,你可能想为此编写一些通用代码,收集开始的任务和然后反复使用Task.WhenAny
。 (您还应该考虑您希望对由于另一个任务返回 false 而与最终结果实际上无关的任务引发的任何异常发生什么。)
【讨论】:
如果第一次调用返回false
,异常将被有效地吞没的明确声明。如果有人使用Task.WhenAny
,则通常会忽略这一点。
谢谢,这非常有用。我实际上并不想同时评估它们,我只是好奇await
是如何工作的。
请注意,如果您想实现对所有内容的评估,可以通过使用&
而不是&&
来更轻松地实现:if (await first_check() & await second_check()) ...
这是因为&
运算符不会绕过任何东西,而&&
在结果明确且不能被后续操作数更改时停止(即,如果第一个操作数已经是false
,则检查第二个操作数没有意义)。 |
和 ||
(逻辑 OR 与快捷方式 OR)也是如此,但这里的快捷方式意味着当第一个操作数为 true
时评估停止。【参考方案2】:
检查起来超级简单。
试试这个代码:
async Task Main()
if (await first_check() && await second_check())
Console.WriteLine("Here?");
Console.WriteLine("Tested");
Task<bool> first_check() => Task.FromResult(false);
Task<bool> second_check() Console.WriteLine("second_check"); return Task.FromResult(true);
它只输出“Tested”而不是别的。
【讨论】:
测试表明 C#允许这种短路(假设使用的编译器是兼容的)。它不显示短路是否必需,即程序员是否可以依赖。 我猜这里的一些上下文是两个await
直接一个接一个放置的语句基本上是按顺序执行的。即使它们用于异步编程,await
s 本身的执行顺序也不会改变。 (如果它们在不同的函数或其他东西中,这显然不成立。)因此它总是会在考虑调用second_check()
之前得到first_check()
的结果。因此,短路评估总是以相同的顺序执行,而不是先评估second_check()
。
@Panzercrisis 另一种思考方式是await
的全部意义在于它使异步函数看起来是同步的。当它们在布尔表达式中使用时,没有理由会有所不同。【参考方案3】:
是的。您可以使用sharplab.io自行检查,如下:
public async Task M()
if(await Task.FromResult(true) && await Task.FromResult(false))
Console.WriteLine();
被编译器有效地转换成类似的东西:
TaskAwaiter<bool> awaiter;
... compiler-generated state machine for first task...
bool result = awaiter.GetResult();
// second operation started and awaited only if first one returned true
if (result)
awaiter = Task.FromResult(false).GetAwaiter();
...
或者作为一个简单的程序:
Task<bool> first_check() => Task.FromResult(false);
Task<bool> second_check() => throw new Exception("Will Not Happen");
if (await first_check() && await second_check())
sharplab.io 的第二个示例。
【讨论】:
【参考方案4】:由于我自己一直在编写编译器,我觉得有资格提供一个更符合逻辑的意见,而不仅仅是基于一些测试。
如今,大多数编译器将源代码转换为 AST(抽象语法树),用于以与语言无关的方式表示源代码。 AST 通常由语法节点组成。产生值的语法节点称为表达式,而不产生任何值的语法节点称为语句。
鉴于问题中的代码,
if (await first_check() && await second_check())
让我们考虑测试条件表达式,即
await first_check() && await second_check()
为此类代码生成的 AST 将类似于:
AndExpression:
firstOperand = (
AwaitExpression:
operand = (
MethodInvocationExpression:
name = "first_check"
parameterTypes = []
arguments = []
)
)
secondOperand = (
AwaitExpression:
operand = (
MethodInvocationExpression:
name = "second_check"
parameterTypes = []
arguments = []
)
)
AST 本身和我用来表示它的语法完全是即时发明的,所以我希望它很清楚。看起来 *** 标记引擎喜欢它,因为它看起来不错! :)
此时,要弄清楚的是解释的方式。好吧,我可以告诉大多数解释器只是分层评估表达式。因此,它将以这种方式完成:
计算表达式await first_check() && await second_check()
计算表达式await first_check()
计算表达式first_check()
解析符号first_check
-
是参考吗?否(否则检查它是否引用了委托。)
是方法名吗?是的(我不包括解析嵌套范围、检查它是否是静态的等内容,因为它是题外话,并且问题中没有提供足够的信息来深入挖掘这些细节。)
评估参数。没有人。因此,将调用名称为first_check
的无参数方法。
调用名为first_check
的无参数方法,其结果将是表达式first_check()
的值。
该值应为Task<T>
或ValueTask<T>
,因为这是一个等待表达式。
await 表达式正在等待获取它最终会产生的值。
and 表达式的第一个操作数是否产生false
?是的。无需计算第二个操作数。
此时,我们知道await first_check() && await second_check()
的值也必然是false
。
我包含的一些检查是静态完成的(即在编译时)。但是,它们的存在是为了让事情更清晰——不用谈论编译,因为我们只是在看评估表达式的方式。
整个事情的本质是 C# 不会关心表达式是否被等待 - 它仍然是 and 表达式的第一个操作数,因此它将首先被评估。然后,只有当它会产生true
时,才会计算第二个操作数。否则,整个 and 表达式被假定为false
,否则不能。
这主要是绝大多数编译器的方式,包括 Roslyn(实际的 C# 编译器,完全使用 C# 编写)和解释器都可以工作,尽管我隐藏了一些无关紧要的实现细节,比如方式await 表达式确实在等待,您可以通过查看生成的字节码来了解自己(您可以使用像 this 这样的网站。无论如何我不隶属于这个网站 - 我只是建议它,因为它使用 Roslyn,我认为这是一个值得记住的好工具。)
澄清一下,await 表达式的工作方式相当复杂,不适合这个问题的主题。它应该得到一个完整的、单独的答案来正确解释,但我认为它并不重要,因为它纯粹是一个实现细节,不会使等待表达式的行为与普通表达式有任何不同。
【讨论】:
我对刚刚收到的反对票很好奇。亲爱的投反对票,您介意评论我的回答以说明投反对票的原因吗?以上是关于C# 是不是使用 await 对 if 语句执行短路评估?的主要内容,如果未能解决你的问题,请参考以下文章
NodeJS - 在 Array.map 中使用 Async/Await 和“if”语句