4

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.

4
  • It does for me, and I'm also using C# 4.0. Commented Apr 11, 2011 at 15:32
  • Oh, nevermind. It compiles and fails at runtime. I was testing on LINQPad, and confused the errors :( Commented Apr 11, 2011 at 15:33
  • You are correct that the entire code piece won't compile, but the first foreach is compiling for me. Commented Apr 11, 2011 at 15:33
  • I fully expect it to fail at runtime, I want to know why it's not corrected at compile time. Commented Apr 11, 2011 at 15:34

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

2 Comments

So if C# allowed for multiple-inheritance, would case 3 pass? It seems like it would.
@L. Moser: Not exactly case 3 as you have it, because StringBuilder is a sealed class, but yes, that's the idea.
1

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.

3 Comments

If this is indeed a covariance/contravariance loophole, please explain in more detail. Specifically, looking at your explanation, I have no way of knowing why the third example doesn't compile.
Right. See also this answer for more on why the second foreach doesn't compile.
I'm sorry, should have explained in more detail. See the answer of Martinho Fernandes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.