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

Foreach processing for types implementing IEnumerable<T> is problematic #1188

Open
svick opened this issue Oct 16, 2024 · 0 comments
Open

Foreach processing for types implementing IEnumerable<T> is problematic #1188

svick opened this issue Oct 16, 2024 · 0 comments

Comments

@svick
Copy link
Contributor

svick commented Oct 16, 2024

§13.9.5 The foreach statement of the current draft-v9 says that when processing foreach, and after not finding a GetEnumerable method (in practice, this happens with explicit interface implementation), we do the following:

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.

I think relying on implicit conversions here is problematic. Also, it's not what Roslyn actually does, resulting in differences between the specification and the implementations.

In particular:

  1. The specification allows implementing IEnumerable<T> multiple times and directly using such type in foreach, when one of the type parameters inherits from the other:

    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();
    }

    According to the spec, the unique type T here should be string, since there is an implicit covariant conversion from IEnumerable<string> to IEnumerable<object>. The compiler does not allow this code:

    error CS1640:: foreach statement cannot operate on variables of type 'MyCollection' because it implements multiple instantiations of 'IEnumerable'; try casting to a specific interface instantiation

  2. The specification results in the wrong iteration type for a type that implements IEnumerable<dynamic>:

     foreach (var x in new MyCollection<dynamic>())
     {
     }
    
     class MyCollection<T> : IEnumerable<T>
     {
         IEnumerator<T> IEnumerable<T>.GetEnumerator() => throw new NotImplementedException();
    
         IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException();
     }

    According to the spec, the unique type T here should be object, since the type is implicitly convertible to both IEnumerable<object> and IEnumerable<dynamic>, but dynamic is explicitly forbidden. The compiler determines the iteration type to be dynamic, as expected.

It seems to me that this could be resolved by following the compiler, and specifying this in terms of the interfaces actually implemented by the type in question, not based on implicit conversions. But I don't know whether that's viable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant