By default, the await is applied on the full expression. This can lead to issues when the result of the awaited task is used as part of the expression. Like in this example:
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
if (await context.ChangeDocumentContext() is { } document && FindRoot(document.Node) is { } root)
{
// handles the root node.
}
}
The FindRoot logic is (potentially) executed before ChangeDocumentContext is fully executed, which potentially leads to a different outcome.
The fix is to add parentheses:
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
if ((await context.ChangeDocumentContext()) is { } document && FindRoot(document.Node) is { } root)
{
// handles the root node.
}
}
A rule that detects this issue would be a great addition (and I am happy to volunteer to implement the rule).
I don’t follow. Per Operators and expressions - List all operators and expression - C# reference | Microsoft Learn , await binds more strongly than is, so await context.ChangeDocumentContext() is { } document is already the same as (await context.ChangeDocumentContext()) is { } document. Also, await (context.ChangeDocumentContext() is { } document) doesn’t compile because bool isn’t awaitable, so it doesn’t need an additional analyzer.
I do recognize that it can be inconvenient that await is a prefix operator, so nested parentheses like var endResult = await (await (await foo.Bar()).Baz()).Baz2(); or splitting up the line may be necessary where a postfix operator would have a nicer var endResult = foo.Bar().await.Baz().await.Baz2().await;. However, omitting these parentheses tends to result in a type error.
Can you provide an example that compiles but does something undesirable?