Skip to content

Commit

Permalink
Prefer pattern-based over interface-based disposal in await using
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv committed Mar 19, 2024
1 parent b45733c commit 5d2578a
Show file tree
Hide file tree
Showing 6 changed files with 1,177 additions and 160 deletions.
23 changes: 23 additions & 0 deletions docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,26 @@ static class C
public static string M(I2 o, in int x) => "2";
}
```

## Prefer pattern-based over interface-based disposal in async `using`

***Introduced in Visual Studio 2022 version 17.10p3***

An async `using` prefers to bind using a pattern-based `DisposeAsync()` method rather than the interface-based `IAsyncDisposable.DisposeAsync()`.

For instance, the public `DisposeAsync()` method will be picked, rather than the private interface implementation:
```csharp
await using (var x = new C()) { }

public class C : System.IAsyncDisposable
{
ValueTask IAsyncDisposable.DisposeAsync() => throw null; // no longer picked
public async ValueTask DisposeAsync()
{
Console.WriteLine("PICKED");
await Task.Yield();
}
}
```

39 changes: 20 additions & 19 deletions src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,31 +183,13 @@ internal static BoundStatement BindUsingStatementOrDeclarationFromParts(SyntaxNo

bool bindDisposable(bool fromExpression, out MethodArgumentInfo? patternDisposeInfo, out TypeSymbol? awaitableType)
{
TypeSymbol disposableInterface = getDisposableInterface(hasAwait);
Debug.Assert((object)disposableInterface != null);

CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = originalBinder.GetNewCompoundUseSiteInfo(diagnostics);
Conversion iDisposableConversion = classifyConversion(fromExpression, disposableInterface, ref useSiteInfo);
patternDisposeInfo = null;
awaitableType = null;

diagnostics.Add(syntax, useSiteInfo);

if (iDisposableConversion.IsImplicit)
{
if (hasAwait)
{
awaitableType = originalBinder.Compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_ValueTask);
}

return !ReportUseSite(disposableInterface, diagnostics, hasAwait ? awaitKeyword : usingKeyword);
}

Debug.Assert(!fromExpression || expressionOpt != null);
TypeSymbol? type = fromExpression ? expressionOpt!.Type : declarationTypeOpt;

// Pattern-based binding
// If this is a ref struct, or we're in a valid asynchronous using, try binding via pattern.
// We won't need to try and bind a second time if it fails, as async dispose can't be pattern based (ref structs are not allowed in async methods)
if (type is object && (type.IsRefLikeType || hasAwait))
{
BoundExpression? receiver = fromExpression
Expand Down Expand Up @@ -250,6 +232,25 @@ bool bindDisposable(bool fromExpression, out MethodArgumentInfo? patternDisposeI
}
}

// Interface binding
TypeSymbol disposableInterface = getDisposableInterface(hasAwait);
Debug.Assert((object)disposableInterface != null);

CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = originalBinder.GetNewCompoundUseSiteInfo(diagnostics);
Conversion iDisposableConversion = classifyConversion(fromExpression, disposableInterface, ref useSiteInfo);

diagnostics.Add(syntax, useSiteInfo);

if (iDisposableConversion.IsImplicit)
{
if (hasAwait)
{
awaitableType = originalBinder.Compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_ValueTask);
}

return !ReportUseSite(disposableInterface, diagnostics, hasAwait ? awaitKeyword : usingKeyword);
}

if (type is null || !type.IsErrorType())
{
// Retry with a different assumption about whether the `using` is async
Expand Down
Loading

0 comments on commit 5d2578a

Please sign in to comment.