由于我自己写过编译器,所以我有资格提供一个更加有逻辑的观点,而不仅仅是基于一些测试。
如今,大多数编译器将源代码转换为抽象语法树(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本身和用来表示它的语法,希望这一点很清楚。看起来StackOverflow标记引擎很喜欢它,因为它看起来很好! :)
此时要解决的问题是如何解释它。嗯,我可以告诉你大多数解释器只是按层次顺序评估表达式。因此,它将按照以下方式完成:
计算表达式await first_check() && await second_check()
计算表达式await first_check()
计算表达式first_check()
解析符号first_check
- 它是一个引用吗?不是(否则请检查它是否引用委托)。
- 它是方法名吗?是(我不包括解决嵌套作用域、检查它是否是静态的等细节,因为这是离题并且提供的问题信息不足以深入挖掘这些细节)。
计算参数。没有任何参数。因此,将调用一个名为first_check
的无参方法。
调用一个名为first_check
的无参方法,其结果将是表达式first_check()
的值。
该值应为Task<T>
或ValueTask<T>
,因为这是等待表达式。
等待等待表达式以获取它最终产生的值。
和表达式的第一个操作数是否产生false
?是的。不必计算第二个操作数。
此时,我们知道await first_check() && await second_check()
的值也必须为false
。
我包含的一些检查是静态完成的(即在编译时完成)。但是,它们存在是为了使事情更清晰 —— 没有必要谈论编译,因为我们只看表达式的评估方式。
整个问题的重点在于C#不会在意表达式是否被等待 ——它仍然是and表达式的第一个操作数,并且按此顺序评估。只有当它产生true
时,第二个操作数才会被计算。否则,整个and表达式被认为是false
,因为它不能是其他情况。
这基本上是大多数编译器(包括Roslyn(实际使用C#完全编写的C#编译器)和解释器)的工作方式,尽管我隐藏了一些不重要的实现细节,例如await表达式的等待方式,您可以通过查看生成的字节码(您可以使用像
这样的网站)自己理解。只是为了澄清,await表达式的工作方式相当复杂,它不适合在这个问题的主题中讨论。它值得一个单独的答案来正确解释,但我认为它并不重要,因为它纯粹是一个实现细节,不会使等待的表达式与正常表达式有任何不同的行为。
if
还是await
都不会影响短路计算。async
关键字除了允许使用await
等待已经执行的异步操作而不阻塞之外,不会影响语言的行为。 - Panagiotis KanavosOperator &&
的哪一部分文档让您认为它会 永远 跳过短路? - Alexei Levenkovif
无关。无论在何处使用,&&
和||
都会执行短路计算,例如some_var = <expression1> && <expression2>
。 - Barmar