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

await foreach utilizing struct enumerator never ends #72820

Open
AlekseyTs opened this issue Mar 30, 2024 · 1 comment
Open

await foreach utilizing struct enumerator never ends #72820

AlekseyTs opened this issue Mar 30, 2024 · 1 comment

Comments

@AlekseyTs
Copy link
Contributor

using System.Threading;
using System.Threading.Tasks;

struct S1
{
    public S2 GetAsyncEnumerator(CancellationToken token = default)
    {
        return new S2();
    }
}

struct S2
{
    bool stop;

    public ValueTask DisposeAsync()
    { 
        System.Console.Write("D");
        return ValueTask.CompletedTask;
    }

    public int Current => 123;
    public async ValueTask<bool> MoveNextAsync()
    {
        if (!stop)
        {
            stop = true;
            return true;
        }

        return false;
    }
}


class C
{
    static async Task Main()
    {
        await Test(new S1());
    }

    static async Task Test(S1 s1)
    {
        await foreach (var i in s1)
        {
            System.Console.Write(i);
        }
    }
}

Expected output: "123D"

Observed: an infinite loop

If S2 is changed to a class, the code behaves as expected. It is quite possible that generated code makes unexpected copies of the struct.

A generic case also exhibits the same problem:

 #nullable disable

using System;
using System.Threading;
using System.Threading.Tasks;

interface ICustomEnumerator
{
    public int Current {get;}

    public ValueTask<bool> MoveNextAsync();
}

interface IGetEnumerator<TEnumerator> where TEnumerator : ICustomEnumerator
{
    TEnumerator GetAsyncEnumerator(CancellationToken token = default);
}

struct S1 : IGetEnumerator<S2>
{
    public S2 GetAsyncEnumerator(CancellationToken token = default)
    {
        return new S2();
    }
}

struct S2 : ICustomEnumerator, IAsyncDisposable
{
    bool stop;

    public ValueTask DisposeAsync()
    { 
        System.Console.Write("D");
        return ValueTask.CompletedTask;
    }

    public int Current => 123;
    public async ValueTask<bool> MoveNextAsync()
    {
        if (!stop)
        {
            stop = true;
            return true;
        }

        return false;
    }
}


class C
{
    static async Task Main()
    {
        await Test<S1, S2>();
    }

    static async Task Test<TEnumerable, TEnumerator>()
        where TEnumerable : IGetEnumerator<TEnumerator>
        where TEnumerator : ICustomEnumerator, IAsyncDisposable
    {
        await foreach (var i in default(TEnumerable))
        {
            System.Console.Write(i);
        }
    }
}
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged Issues and PRs which have not yet been triaged by a lead label Mar 30, 2024
@AlekseyTs
Copy link
Contributor Author

CC @jcouv

@jaredpar jaredpar added this to the 17.11 milestone Apr 1, 2024
@jaredpar jaredpar added Bug and removed untriaged Issues and PRs which have not yet been triaged by a lead labels Apr 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants