Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistent dispose behavior between await using and await foreach #72573

Closed
AlekseyTs opened this issue Mar 16, 2024 · 3 comments · Fixed by #72598
Closed

Inconsistent dispose behavior between await using and await foreach #72573

AlekseyTs opened this issue Mar 16, 2024 · 3 comments · Fixed by #72598
Assignees
Labels
4 - In Review A fix for the issue is submitted for review. Area-Compilers Area-Language Design Feature - Async Streams Async Streams untriaged Issues and PRs which have not yet been triaged by a lead
Milestone

Comments

@AlekseyTs
Copy link
Contributor

Execute the following code and examine its output (sharplab):

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

class C
{
    public static async Task Main()
    {
        Console.Write("Using dispose ");
        await using (new AsyncEnumerator())
        {
        }

        Console.Write("Foreach dispose ");
        await foreach (var i in new AsyncEnumerable())
        {
        }
    }
}

struct AsyncEnumerable : IAsyncEnumerable<int>
{
    public AsyncEnumerator GetAsyncEnumerator(CancellationToken token = default) => new AsyncEnumerator();
    IAsyncEnumerator<int> IAsyncEnumerable<int>.GetAsyncEnumerator(CancellationToken token) => new AsyncEnumerator();
}

struct AsyncEnumerator : IAsyncEnumerator<int>
{
    public int Current => 0;
    public async ValueTask<bool> MoveNextAsync()
    {
        await Task.Yield();
        return false;
    }
    public async ValueTask DisposeAsync()
    {
        Console.WriteLine("Pattern");
        await Task.Yield();
    }

    int IAsyncEnumerator<int>.Current => throw null;
    ValueTask<bool> IAsyncEnumerator<int>.MoveNextAsync() => throw null;
    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        Console.WriteLine("Interface");
        await Task.Yield();
    }
}

Observed: Using gives priority to IAsyncDisposable implementation, foreach ignores IAsyncDisposable implementation.

Using dispose Interface
Foreach dispose Pattern

Expected: Both give priority to IAsyncDisposable implementation

Using dispose Interface
Foreach dispose Interface
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged Issues and PRs which have not yet been triaged by a lead label Mar 16, 2024
@AlekseyTs
Copy link
Contributor Author

@jcouv FYI

@jcouv jcouv self-assigned this Mar 16, 2024
@jcouv jcouv added the Bug label Mar 16, 2024
@jcouv jcouv added this to the 17.10 milestone Mar 16, 2024
@jcouv
Copy link
Member

jcouv commented Mar 18, 2024

This seems to be by-design and explicitly tested (DisposePatternPreferredOverIAsyncDisposable). See related issue #59955
The spec says we should bind a pattern-based DisposeAsync first, and if that doesn't work then try converting to interface, and if that doesn't work emit an empty finally: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/async-streams.md#semantics

@jcouv jcouv added Resolution-By Design The behavior reported in the issue matches the current design and removed Bug labels Mar 18, 2024
@AlekseyTs
Copy link
Contributor Author

AlekseyTs commented Mar 18, 2024

I saw the tests, the behavior didn't look sound to me anyway.

@jcouv jcouv added Area-Language Design and removed Resolution-By Design The behavior reported in the issue matches the current design labels Mar 19, 2024
@jcouv jcouv added the 4 - In Review A fix for the issue is submitted for review. label Mar 20, 2024
@jcouv jcouv reopened this Apr 1, 2024
@jcouv jcouv modified the milestones: 17.10, 17.11 Apr 1, 2024
@jcouv jcouv closed this as completed Apr 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4 - In Review A fix for the issue is submitted for review. Area-Compilers Area-Language Design Feature - Async Streams Async Streams untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants