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

Foreaching over collections with compatible IEnumerable<T> implementations is disallowed by the compiler #76598

Open
jnm2 opened this issue Jan 2, 2025 · 1 comment

Comments

@jnm2
Copy link
Contributor

jnm2 commented Jan 2, 2025

(Thanks to @svick at dotnet/csharpstandard#1188)

In C# language version 5, the spec was updated to allow foreaching over collections with multiple IEnumerable<T> implementations so long as the chosen implementation could be implicitly converted to all the others. I can see the rationale in this spec change: there is really no confusion about "which sequence" you're getting. The sequence enumerated with the more-derived item type should contain the same instances as when enumerating with the less-derived. If there is any confusion, that's poor type authoring.

However, the compiler doesn't seem like it was updated to match this spec change. It continues to implement the behavior as specified in v4:

// ❌ CS1640 foreach statement cannot operate on variables of type 'MyCollection' because it implements multiple
// instantiations of 'IEnumerable<T>'; try casting to a specific interface instantiation
foreach (var x in new MyCollection())
{
}

class MyCollection : IEnumerable<string>, IEnumerable<object>
{
    IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException();

    IEnumerator<string> IEnumerable<string>.GetEnumerator() => throw new NotImplementedException();

    IEnumerator<object> IEnumerable<object>.GetEnumerator() => throw new NotImplementedException();
}

V5 spec, which allows the above example without compile errors, with an element type of string, which is identical to the current spec:

If among all the types Tᵢ for which there is an implicit conversion from X to IEnumerable<Tᵢ>, there is a unique type T such that T is not dynamic and for all the other Tᵢ there is an implicit conversion from IEnumerable<T> to IEnumerable<Tᵢ>, then the collection type is the interface IEnumerable<T>, the enumerator type is the interface IEnumerator<T>, and the iteration type is T.

V4 spec (PDF), which the compiler still follows:

If there is exactly one type T such that there is an implicit conversion from X to the interface System.Collections.Generic.IEnumerable<T>, then the collection type is this interface, the enumerator type is the interface System.Collections.Generic.IEnumerator<T>, and the element type is T.

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Jan 2, 2025
@jaredpar jaredpar removed the untriaged Issues and PRs which have not yet been triaged by a lead label Jan 6, 2025
@jaredpar jaredpar added this to the Backlog milestone Jan 6, 2025
@jaredpar
Copy link
Member

jaredpar commented Jan 6, 2025

I agree the compiler implementation deviates from the spec here. At the same time, it's been a decade since this spec change happened, the compiler hasn't implemented it and we've gotten next to no customer complaints about it. Given that maybe the best course of action is to change the spec to reflect the compiler.

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

2 participants