繁体   English   中英

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

[英]Why there is a restriction for explicit casting a generic to a class type but there is no restriction for casting a generic to an interface type?

在阅读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;

   }
}

当你尝试在没有继承关系的类之间进行转换时,这正是你在正常情况下得到的 - 没有泛型 -

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

最大的区别是接口保证是参考类型。 价值类型是麻烦制造者。 它在C#语言规范第6.2.6章中有明确提及,其中有一个很好的例子来说明问题:


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

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

如果允许将t直接显式转换为int,则可能很容易预期XF(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)将在运行时抛出异常,因为boxed int无法直接转换为long。

它没有错。 唯一的区别是,在第一种情况下,编译器可以在编译时检测到哪里没有可能的转换,但他不能对接口如此“确定”,因此在这种情况下,错误仅在运行时才会上升。 所以,

// 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;
        }
    }
}

我认为转换为接口和转换为类之间的区别在于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!
}

仿制药也是如此。

希望这可以帮助。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM