dotnet / dotnet/csharpstandard
Foreach processing for types implementing IEnumerable<T> is problematic
- Dominant language
- C#
- Stars
- 815
- Forks
- 99
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 16
Description
[§13.9.5 *The foreach statement* of the current draft-v9](https://github.com/dotnet/csharpstandard/blob/4ad7d5b7/standard/statements.md#1395-the-foreach-statement) 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`, 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` to `IEnumerable`, then the collection type is the interface `IEnumerable`, the enumerator type is the interface `IEnumerator`, 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` multiple times and directly using such type in `foreach`, when one of the type parameters inherits from the other:
```c#
foreach (var x in new MyCollection())
{
}
class MyCollection : IEnumerable, IEnumerable
{
IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException();
IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException();
IEnumerator IEnumerable.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` to `IEnumerable`. 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`:
```c#
foreach (var x in new MyCollection())
{
}
class MyCollection : IEnumerable
{
IEnumerator IEnumerable.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` and `IEnumerable`, 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.
Contributor guide
Assessment
This issue has not been assessed yet.