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

Ability to obsolete IEnumerable.GetEnumerator used through Linq #103664

Closed
verdie-g opened this issue Jun 18, 2024 · 7 comments
Closed

Ability to obsolete IEnumerable.GetEnumerator used through Linq #103664

verdie-g opened this issue Jun 18, 2024 · 7 comments

Comments

@verdie-g
Copy link
Contributor

verdie-g commented Jun 18, 2024

Assume a class that reads rows from the network

class MyDbReader : IEnumerable<object>, IAsyncEnumerable<object>
{
    [Obsolete]
    public IEnumerator<object> GetEnumerator()
    {
        yield return new object();
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    
    public async IAsyncEnumerator<object> GetAsyncEnumerator(CancellationToken cancellationToken = new())
    {
        yield return new object();
    }
}

the maintainer recently implemented IAsyncEnumerable after realizing that reading synchronously from the network using the IEnumerable was causing a thread pool starvation. They also added the Obsolete attribute on the GetEnumerator hoping this would guide the users to use GetAsyncEnumerator. Though, if used through a foreach or Linq, no warnings are emitted:

MyDbReader r = new();
_ = r.GetEnumerator(); // Method 'MyDbReader.GetEnumerator()' is obsolete
_ = r.Select(x => x); // No warnings.
foreach (var x in r) // No warnings
{
}

When GetEnumerator is marked as obsolete, would it possible to generate warnings on foreach and Linq?

@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Jun 18, 2024
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-linq
See info in area-owners.md if you want to be subscribed.

@JakeYallop
Copy link
Contributor

Would this be something fixed by dotnet/roslyn#73920 and #103430, at least for the foreach case?

@eiriktsarpalis
Copy link
Member

I'm seeing warnings in the foreach case:

image

I don't think it would be possible to warn for the case of the Select since the compiler can't possibly reason about the enumerator being used (it isn't in your case).

@verdie-g verdie-g changed the title Ability to obsolete IEnumerable.GetEnumerator used through foreach/Linq Ability to obsolete IEnumerable.GetEnumerator used through Linq Jun 18, 2024
@verdie-g
Copy link
Contributor Author

verdie-g commented Jun 18, 2024

I'm seeing warnings in the foreach case:

ha, indeed I see the warning in the build output but not in Rider. I'll redirect that issue to JetBrains (RIDER-113674).

I don't think it would be possible to warn for the case of the Select since the compiler can't possibly reason about the enumerator being used

In the expression x.Select, in the case where x is not an IEnumerable (e.g. a MyDbReader), I think an analyzer could check if the GetEnumerator is obsolete, right?

But yes it would miss such case:

IEnumerable<object> e = new MyDbReader();
_ = e.Select(x => x);

@eiriktsarpalis
Copy link
Member

eiriktsarpalis commented Jun 18, 2024

I think an analyzer could check if the GetEnumerator is obsolete, right?

It could, but like I said technically calling Select doesn't call GetEnumerator. In my view it doesn't seem like a particularly common pattern for us to invest in adding an analyzer.

@verdie-g
Copy link
Contributor Author

technically calling Select doesn't call GetEnumerator

Hmm true.

In my view it doesn't seem like a particularly common pattern for us to invest in adding an analyzer.

With the introduction of IAsyncEnumerable in .NET Core 3, I feel like this could a great feature to migrate from one to the other. I've seen a bunch of libraries that mistakenly do sync-over-async because of their IEnumerable implementation (example in CassandraCsharpDriver).

@eiriktsarpalis
Copy link
Member

My recommendation would be to use separate types for each of the interface implementations, or perhaps use a property or factory that explicitly gets the specific enumerator implementation

public IEnumerable<T> AsEnumerable();
public IAsyncEnumerable<T> AsAsyncEnumerable();

@eiriktsarpalis eiriktsarpalis closed this as not planned Won't fix, can't repro, duplicate, stale Jun 18, 2024
@dotnet-policy-service dotnet-policy-service bot removed the untriaged New issue has not been triaged by the area owner label Jun 18, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Jul 19, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants