Why is the C# compiler happy with double IEnumerable<T> and foreach T?

跟風遠走 提交于 2019-12-22 01:34:38

问题


I know this code does not work (and have no problems writing it in a way that will work). I was wondering how the compiler can build with out any errors. And you get run time errors if you where to run it? ( assuming data was not null )

using System;
using System.Collections.Generic;

public class Class1
{
    public void Main()
    {
        IEnumerable<IEnumerable<Foo>> data = null;

        foreach(Foo foo in data){
            foo.Bar();
        }
    }

}

public class Foo {
    public void Bar() { }
}

回答1:


This is because foreach does not do compile time checking in your specific case. If you built working code you get a InvalidCastException at run-time.

using System.Collections.Generic;

public class Test
{
    internal class Program
    {
        public static void Main()
        {
            var item = new Foo();
            var inner = new List<Foo>();
            var outer = new List<List<Foo>>();

            inner.Add(item);
            outer.Add(inner);

            IEnumerable<IEnumerable<Foo>> data = outer;

            foreach (Foo foo in data)
            {
                foo.Bar();
            }
        }

    }


    public class Foo
    {
        public void Bar()
        {
        }
    }
}

doing foreach (Foo foo in data) is equivalent to calling

IEnumerator enumerator = ((IEnumerable)data).GetEnumerator();
Foo foo; //declared here in C# 4 and older
while(enumerator.MoveNext())
{
    //Foo foo; //declared here in C# 5 and newer

    foo = (Foo)enumerator.Current; //Here is the run time error in your code.

    //The code inside the foreach loop.
    {
        foo.Bar();
    }
}

So you see it does not care what type you passed in, as long as the foo = (Foo)enumerator.Current; call succeeds.


The reason it does not throw any compile time errors is IEnumerable<T> is covariant. That means I am allowed to pass any class that is based on Foo or more derived from Foo. So if I could potentially make a 2nd class that inherits from Foo that would also support IEnumerable<Foo> and have my list contain that instead it would cause the cast to fail.

//This code compiles fine in .NET 4.5 and runs without throwing any errors.
internal class Program
{
    public static void Main()
    {
        var item = new Baz();
        var inner = new List<Baz>();
        inner.Add(item);

        IEnumerable<IEnumerable<Foo>> data = inner;

        foreach (Foo foo in data)
        {
            foo.Bar();
        }
    }
}

public class Foo
{
    public void Bar()
    {
    }
}

public class Baz : Foo, IEnumerable<Foo>
{
    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }

    IEnumerator<Foo> IEnumerable<Foo>.GetEnumerator()
    {
        throw new NotImplementedException();
    }
}

However if you mark Foo as sealed the compiler now knows that no more derived classes could exist and then will throw the compiler error




回答2:


Since you specified a type explicitly, each iteration of foreach will attempt to cast (at runtime) the current item to Foo. It is no different than writing this:

IEnumerable<IEnumerable<Foo>> data = null;
foreach (object item in data)
{
    Foo foo = (Foo)item;
    foo.Bar();
}

Or more directly, this:

IEnumerable<Foo> data = null;
Foo foo = (Foo)data;

The compiler doesn't complain for the same reason it doesn't complain about a cast to or from an interface: it cannot prove that the cast is invalid (see here for an example of why).

Note that, in contrast, if you work with a concrete class instead of the interface IEnumerable, then you will get a compile-time error. For example:

IEnumerable<List<Foo>> data = null;

foreach(Foo foo in data){  // compile-time error here: "cannot convert List<Foo> to Foo"
    foo.Bar();
}



回答3:


Very good question! this is an open issue I read about it from @Jon Skeet : Do "type-safe" and "strongly typed" mean the same thing? and I have read also some blog in msdn. @slaks blog also nice http://blog.slaks.net/2011/09/c-is-not-type-safe.html




回答4:


You can make the compile time error happen by marking your Foo class as sealed:

public sealed class Foo
{
    public void Bar() { }
}

Otherwise, compiler cannot be sure that conversion is possible.



来源:https://stackoverflow.com/questions/20204925/why-is-the-c-sharp-compiler-happy-with-double-ienumerablet-and-foreach-t

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!