为什么C#编译器允许在IEnumerable <T>和TAlmostAnything之间进行显式转换?

下面的代码给你一个编译器错误,如你所期望的那样:

List<Banana> aBunchOfBananas = new List<Banana>(); Banana justOneBanana = (Banana)aBunchOfBananas; 

但是,使用IEnumerable<Banana> ,您只会得到一个运行时错误。

 IEnumerable<Banana> aBunchOfBananas = new List<Banana>(); Banana justOneBanana = (Banana)aBunchOfBananas; 

为什么C#编译器允许这样做?

我想这是因为IEnumerable<T>是一个接口,其中一些实现可以有一个明确的转换Banana – 不pipe多么愚蠢的。

另一方面,编译器知道List<T>不能被明确地转换为Banana

不错的select,顺便说一下!

添加一个例子来澄清。 也许我们会有一些“enumerable”应该总是包含一个Banana

 public class SingleItemList<T>:Banana, IEnumerable<T> where T:Banana { public static explicit operator T(SingleItemList<T> enumerable) { return enumerable.SingleOrDefault(); } // Others omitted... } 

那么你可以这样做:

 IEnumerable<Banana> aBunchOfBananas = new SingleItemList<Banana>(); Banana justOneBanana = (Banana)aBunchOfBananas; 

与编写以下代码相同,编译器非常满意:

 Banana justOneBanana = aBunchOfBananas.SingleOrDefault(); 

当你说Y y = (Y)x; 这个演员对编译器说:“相信我,无论x是什么,在运行时它都可以被转换成Y ,所以,就这样做吧,好吗?

但是当你说

 List<Banana> aBunchOfBananas = new List<Banana>(); Banana justOneBanana = (Banana)aBunchOfBananas; 

编译器可以查看每个具体类( BananaList<Banana> )的定义,并且看到没有定义static explicit operator Banana(List<Banana> bananas) (请记住,必须在铸造types或铸造types,这是从规格,第17.9.4节)。 它在编译时知道你所说的话永远不会是真的。 所以它喊你停止说谎。

但是当你说

 IEnumerable<Banana> aBunchOfBananas = new List<Banana>(); Banana justOneBanana = (Banana)aBunchOfBananas; 

好吧,现在编译器不知道。 非常好的情况是,无论aBunchOfBananas碰巧在运行时,它的具体typesX可以定义static explicit operator Banana(X bananas) 。 所以编译器相信你,就像你问的那样。

这可能是因为编译器知道 Banana没有扩展List<T> ,但是有可能实现IEnumerable<T>某个对象也可能扩展Banana并使其成为有效的types。

根据语言规范(6.2.4)“明确的引用转换是:从任何typesS到任何接口typesT,如果S不是密封的,并且S没有实现T …明确的引用转换是那些需要运行时检查以确保它们正确的引用types之间的转换…“

所以编译器在编译时不检查接口的实现。 它在运行时执行CLR。 它检查元数据,试图在课堂或其父母中find实现。 我不知道为什么它的行为是这样的。 可能需要很多时间。 所以这段代码编译正确:

 public interface IInterface {} public class Banana { } class Program { static void Main( string[] args ) { Banana banana = new Banana(); IInterface b = (IInterface)banana; } } 

另一方面,如果我们试图将香蕉投入课堂,编译器检查其元数据并抛出一个错误:

  FileStream fs = (FileStream)banana;