-
Notifications
You must be signed in to change notification settings - Fork 92
Description
§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 fromXtoIEnumerable<Tᵢ>, there is a unique typeTsuch thatTis notdynamicand for all the otherTᵢthere is an implicit conversion fromIEnumerable<T>toIEnumerable<Tᵢ>, then the collection type is the interfaceIEnumerable<T>, the enumerator type is the interfaceIEnumerator<T>, and the iteration type isT.
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:
-
The specification allows implementing
IEnumerable<T>multiple times and directly using such type inforeach, 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
There should bestring, since there is an implicit covariant conversion fromIEnumerable<string>toIEnumerable<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
-
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
There should beobject, since the type is implicitly convertible to bothIEnumerable<object>andIEnumerable<dynamic>, butdynamicis explicitly forbidden. The compiler determines the iteration type to bedynamic, 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.