2012-02-08 53 views
59

下面的代码给你一个编译器错误,如你所期望:为什么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#编译器允许这样做?

+0

的事情列表不是一个单一的东西。 – 2012-02-08 17:07:38

+2

虽然不允许使用值类型。 – ken2k 2012-02-08 17:08:07

+1

这也适用于IList 。这就是在下面的答案中,让整个界面与具体类在我的点击。 – deepee1 2012-02-08 22:34:29

回答

48

我想这是因为IEnumerable<T>是一个接口,其中一些实施可能有一个显式的Banana - 无论多么愚蠢,这将是。

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

不错的选择的例子,顺便说一句!

添加一个示例来澄清。也许我们会有一些“枚举”,应始终包含最多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(); 
+4

你不能这样做 - 它不会找到运营商。一个例子是'班SingleBanana:香蕉,IEnumerable '。这就是为什么这只适用于接口 - 因为如果这两种类型都是类的,那么在编译时就可以确定这是不可能的。 – Random832 2012-02-08 20:04:27

+0

@ Random832良好的捕获,但实际上你的类签名也不会工作(你不能有一个明确的转换到基类)。我已经按照预期版本更新了我的答案。 – Yuck 2012-02-08 20:17:09

+0

呃,我的观点是你不能使用运算符超载_at all_为此。它工作的原因是因为派生类对象是_inherently_可转换为基类的。 – Random832 2012-02-09 00:45:14

16

这可能是因为编译器知道Banana没有延伸List<T>,但有可能某些实现了IEnumerable<T>的对象也可能延伸Banana并进行有效的转换。

+1

很遗憾看到唯一正确的答案,在第一个帖子中发布,没有那么多upvotes。也许短代码片段会有所帮助。 – 2013-08-21 00:43:35

29

当你说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)(记住,显式的转换必须定义无论是铸造类型还是铸造类型,都来自规范第17.9.4节)。它在编译时知道你所说的话永远不会是真的。所以它喊你停止说谎。

但是,当你说

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

那么,现在的编译器不知道。很好的情况是,不管aBunchOfBananas碰巧在运行时,它的具体类型X可能已经定义了static explicit operator Banana(X bananas)。所以编译器相信你,就像你问的那样。

+0

这是downvoted? – jason 2012-02-08 21:12:14

+0

也许是因为你没有提到你在类和整数之间做了区分?不知道的是,你的第一个(X和Y)和第二个例子完全一样。 – 2012-02-14 19:24:31

+1

是的,这是downvoted ...这是错的。在运行时无法找到转换运算符(除非涉及'dynamic'关键字)。有另一个downvote。 – 2013-08-21 00:40:21

0

根据语言规范(6.2.4) “显式引用转换是: 从任意类型S至任意接口类型T,只要S没有被密封和提供S不实现Ť .. 显式引用转换是那些需要运行时检查以确保它们正确的引用类型之间的转换......“

因此编译器在编译期间不检查接口实现。它在运行时执行CLR。它会检查元数据,试图在课堂或其父母中找到实现。我不知道为什么它的行为如此。可能需要很长时间。所以这个代码编译正确:

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; 
相关问题