2011-11-01 76 views
18

在阅读微软的文档,我无意中发现了这样一个有趣的代码示例:为什么显式将泛型转换为类类型有限制,但是将泛型转换为接口类型没有限制?

interface ISomeInterface 
{...} 
class SomeClass 
{...} 
class MyClass<T> 
{ 
    void SomeMethod(T t) 
    { 
     ISomeInterface obj1 = (ISomeInterface)t;//Compiles 
     SomeClass  obj2 = (SomeClass)t;  //Does not compile 
    } 
} 

这意味着你可以明确地但不投你的通用的接口类,除非你有一个约束。那么,我仍然无法理解这个决定背后的逻辑,因为接口和类的类型转换都在抛出异常,那么为什么只能保护这些异常呢?

BTW-周围有编译错误的方式,但是这并不在我的脑海中删除逻辑混乱:

class MyOtherClass 
{...} 

class MyClass<T> 
{ 

    void SomeMethod(T t) 

    { 
     object temp = t; 
     MyOtherClass obj = (MyOtherClass)temp; 

    } 
} 
+0

出于好奇:你能输入“SomeClass obj2 =(SomeClass)(object)t;”? –

+0

是的,这是由第二个片段 –

+1

完成检查这个http://philipm.at/2011/1014/,发现它时,试图找到解释。扰乱警报 - 可能会让你更加困惑!;) –

回答

5

这正是你在正常情况下取得 - 没有泛型 - 当您尝试投类没有继承关系之间:

public interface IA 
{ 
} 

public class B 
{ 
} 

public class C 
{ 
} 

public void SomeMethod(B b) 
{ 
    IA o1 = (IA) b; <-- will compile 
    C o2 = (C)b; <-- won't compile 
} 

因此,没有约束,泛型类的行为就好像有类之间没有任何关系。

继续...

好吧,让我们说有人做这样的:

public class D : B, IA 
{ 
} 

,然后调用:

SomeMethod(new D()); 

现在,你就会明白为什么编译器让接口投通。它在编译时确实无法知道接口是否被实现。

请记住,D类很可能是编写它的人使用你的程序集编写的。所以编译器不可能拒绝编译它。它必须在运行时检查。

+0

好点,但它仍然混淆为什么他们选择允许一个演员,并禁止第二个? –

+0

更新了我的答案以包含该内容。 –

+0

如果你尝试'D o3 =(D)b;'SomeMethod(B b)''会编译?这让我感到同样的观点也适用这就是你传递可能是一个后裔,其有效期为演员,但可能不会是......(是的,我是懒惰,而不是只是想它自己的时刻)。 – Chris

1

没有问题。唯一的区别是,在第一种情况下,编译器可以在编译时检测到,那里没有可能的强制转换,但是他不能对接口进行如此“确定”,因此在这种情况下,错误只会在运行时。所以,

// Compiles 
ISomeInterface obj1 = (ISomeInterface)t; 

// Сompiles too! 
SomeClass obj2 = (SomeClass)(object)t;  

将在运行时产生相同的错误。

所以原因可能是:编译器不知道类实现了哪些接口,但它知道类继承(因此(SomeClass)(object)t方法有效)。换句话说:在CLR中禁止无效投射,唯一的区别是在某些情况下,它可以在编译时检测到,而在某些情况下则不能。这背后的主要原因是,即使编译器知道所有类的接口,它也不知道它的后代,它可以实现它,并且对于T有效。请考虑以下情形:

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      MyClass<SomeClass> mc = new MyClass<SomeClass>(); 

      mc.SomeMethod(new SomeClassNested()); 

     } 
    } 

    public interface ISomeInterface 
    { 
    } 

    public class SomeClass 
    { 

    } 

    public class SomeClassNested : SomeClass, ISomeInterface 
    { 

    } 

    public class MyClass<T> 
    { 
     public void SomeMethod(T t) 
     { 
      // Compiles, no errors at runtime 
      ISomeInterface obj1 = (ISomeInterface)t; 
     } 
    } 
} 
+0

为什么你觉得他不知道吗?所有内容都写入元数据表中的程序集中。 –

+0

@衣服好,一切都在组装,这是肯定的,但这并不意味着编译器知道它。编译器是比较“愚蠢”的东西。 –

+0

你能否链接一些对编译器的引用,而不能找出类实现的接口?这听起来不对,但我很乐意接受有关这方面的教育。 – Chris

0

我觉得铸件之间的差异的接口,并铸造 一类在于,C#支持多个“继承” 只对接口的事实。那是什么意思?编译器只能在编译时确定 是否对类 有效,因为C#不允许对类进行多重继承。

另一方面,编译器在编译时不知道是否你的类实现了在转换中使用的接口。为什么? 有人可以从你的班级继承并实施你演员中使用的界面 。所以,编译器在编译时并不知道这一点。 (见下面的SomeMethod4())。

但是编译器能够确定如果您的类是密封的,则向接口转换的 是否有效。

请看下面的例子:

interface ISomeInterface 
{} 
class SomeClass 
{} 

sealed class SealedClass 
{ 
} 

class OtherClass 
{ 
} 

class DerivedClass : SomeClass, ISomeInterface 
{ 
} 

class MyClass 
{ 
    void OtherMethod(SomeClass s) 
    { 
    ISomeInterface t = (ISomeInterface)s; // Compiles! 
    } 

    void OtherMethod2(SealedClass sc) 
    { 
    ISomeInterface t = (ISomeInterface)sc; // Does not compile! 
    } 

    void OtherMethod3(SomeClass c) 
    { 
    OtherClass oc = (OtherClass)c; // Does not compile because compiler knows 
    }        // that SomeClass does not inherit from OtherClass! 

    void OtherMethod4() 
    { 
    OtherMethod(new DerivedClass()); // In this case the cast to ISomeInterface inside 
    }         // the OtherMethod is valid! 
} 

这同样适用于仿制药真的。

希望,这有助于。

2

最大的区别是接口保证是一个引用类型。价值类型是麻烦制造者。它是在C#语言规范,章节6.2.6中明确提到,与演示该问题一个极好的例子:


上述规则不允许从不受约束的类型参数的直接显式转换到非接口类型,这可能令人惊讶。这条规则的原因是为了防止混淆,并明确这种转换的语义。例如,请考虑下面的声明:

class X<T> 
{ 
    public static long F(T t) { 
     return (long)t;    // Error 
    } 
} 

如果T的直接显式转换为int被允许,一个可能很容易想到的是X.F(7)将返回7L。但是,它不会,因为只有在编译时已知数字类型时才考虑标准数字转换。为了使语义更清楚,上面的例子必须改为被写成:

class X<T> 
{ 
    public static long F(T t) { 
     return (long)(object)t;  // Ok, but will only work when T is long 
    } 
} 

此代码现在将编译,但执行XF(7)随后将抛出异常在运行时,由于装箱的int不能被直接转换很长。

相关问题