为什么显式地将泛型强制转换为类类型有限制,但将泛型强制转换为接口类型没有限制?

时间:2011-11-01 08:50:40

标签: c# generics

在阅读Microsoft文档时,我偶然发现了一个有趣的代码示例:

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;

   }
}

4 个答案:

答案 0 :(得分: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类可能很好地由使用你的程序集的人编写 - 在编译之后的几年。所以编译器不可能拒绝编译它。必须在运行时检查它。

答案 1 :(得分: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
    }
}

此代码现在将编译,但执行X.F(7)会在运行时抛出异常,因为盒装的int不能直接转换为long。

答案 2 :(得分: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;
        }
    }
}

答案 3 :(得分: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!
}

仿制药也是如此。

希望,这有帮助。