为什么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;
编译器可以查看每个具体类( Banana
和List<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;