由于我一直在自己编写编译器,因此我有资格提供更多的逻辑意见,而不仅仅是基于某些测试。
如今,大多数编译器将源代码转换为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
,其结果将是expression的值first_check()
。
该值应为aTask<T>
或ValueTask<T>
,因为这是一个await表达式。
正在等待await表达式以获取最终将产生的值。
和表达式的第一个操作数产生false
吗?是。无需评估第二个操作数。
至此,我们知道的价值await first_check() && await second_check()
必定也将false
如此。
我包括的某些检查是静态完成的(即,在编译时)。但是,它们的存在是为了使事情更清楚—无需谈论编译,因为我们只是在考虑表达式的求值方式。
整件事的本质是C#不在乎表达式是否在等待-它仍然是and表达式的第一个操作数,因此它将首先被求值。然后,仅当它将产生true
第二个操作数时,才会对其进行评估。否则,将整个和表达式假定为false
,否则就不能这样。
这主要是绝大多数编译器(包括Roslyn(实际的C#编译器,完全使用C#编写))和解释器的工作方式,尽管我已经隐藏了一些无关紧要的实现细节,例如等待表达式的方式真的等了,你可以通过查看生成的字节码了解自己(你可以使用一个网站,像这样,我无论如何不附属于这个网站-我只是建议,因为它使用了罗斯林,我认为这是一个不错的要牢记的工具。)
需要澄清的是,等待表达式工作的方式相当复杂,因此不适合该问题的主题。它应该有一个完整的,分开的答案来进行正确的解释,但是我不认为它很重要,因为它纯粹是实现细节,不会使等待的表达式的行为与正常表达式有所不同。
if
也不await
影响短路。async
除了允许await
使用之外,不影响语言的行为,以等待已经执行的异步操作而不会阻塞。