Reputation: 6444
As I was refactoring some code this morning, I noticed some weird behavior. I was iterating over a collection of type A
. The declaration and usage of the Enumerable were split (I was declaring and defining a variable using some Linq, then iterating over it later via foreach). However, when I changed the type of the enumerable from IEnumerable<A>
to IEnumerable<B>
, I left the foreach as the following where enumerable was of type IEnumerable<B>
.
IEnumerable<B> enumerable = someEnumerableOfB
foreach(A a in enumerable)
Following is a contrived example of the behavior I found:
IEnumerable<IEnumerable> enumerables = Enumerable.Range(1, 5).Select(x => new List<int> { x });
foreach (StringComparer i in enumerables) //this compiles
{
//do something here
}
foreach (int i in enumerables) //this doesn't compile
{
//do something here
}
IEnumerable<StringBuilder> stringBuilders = Enumerable.Range(1, 5).Select(x => new StringBuilder(x.ToString()));
foreach (FileStream sb in stringBuilders) //this doesn't compile
{
//do something here
}
I was surprised to see the first one compile. Can someone explain exactly why this works? I assume it has something to do with the fact that the IEnumerable
is of an interface, but I can't explain it.
Upvotes: 4
Views: 230
Reputation: 234424
According to the algorithm described section §15.8.4. of the specification, the compiler will expand the foreach
into the following:
{
IEnumerator<IEnumerable> e = ((IEnumerable<IEnumerable>)(x)).GetEnumerator();
try
{
StringComparer v;
while (e.MoveNext())
{
v = (StringComparer)(IEnumerable)e.Current; // (*)
// do something here
}
}
finally
{
// Dispose of e
}
}
The line I've marked with an asterisk is the reason why it compiles for the first and not for the second. That is a valid cast because you can have a subclass of StringComparer
that implements IEnumerable
. Now change it to:
v = (int)(IEnumerable)e.Current; // (*)
And it doesn't compile, because this is not a valid cast: int
does not implement IEnumerable
, and it can't have any subclasses.
Upvotes: 4
Reputation: 15130
Because the compiler cant know whats in the Enumerable but it knows that it cant be a value type (e.g. int).
foreach (StringComparer i in enumerables) will compile since StringComparer is a reference type and for the compilers sake might just be in enumerables.
Upvotes: 1