问题
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