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

时间:2012-02-08 17:04:25

标签: c# .net generics compiler-errors

以下代码为您提供编译器错误,正如您所期望的那样:

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

Banana justOneBanana = (Banana)aBunchOfBananas;

但是,使用IEnumerable<Banana>时,只会出现运行时错误。

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

Banana justOneBanana = (Banana)aBunchOfBananas;

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

4 个答案:

答案 0 :(得分: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();

答案 1 :(得分: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)。所以编译器会像你要求的那样信任你。

答案 2 :(得分:16)

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

答案 3 :(得分:0)

根据语言规范(6.2.4) “明确的参考转换是:  从任何类型S到任何接口类型T,如果S未密封且提供S未实现T. ... 显式引用转换是引用类型之间的转换,需要运行时检查才能确保它们正确...“

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

public interface IInterface
{}

public class Banana
{
}

class Program
{
    static void Main( string[] args )
    {
        Banana banana = new Banana();

        IInterface b = (IInterface)banana;
    }
}

另一方面,如果我们尝试将banana转换为类,编译器会检查其元数据并抛出错误:

 FileStream fs = (FileStream)banana;