【问题标题】:Casting base class to derived class [duplicate]将基类转换为派生类[重复]
【发布时间】:2020-01-08 19:49:12
【问题描述】:

基本上有2个类。

class Motor {
   int a;
   int b;
   int c;
}

class Title : Motor {
    int d;
    int e;
}

现在传递一个带有 Motor 类实例的函数。

bool AssignNums(Motor m)
{
   Title t = (Title)m;   //throws exception
   //Do other things with "t"
}

它的调用来自,

void AddNums()
{
   Motor m = new Motor();
   m.a = 23;
   m.b = 78;
   m.c = 109;
   AssignNums(m);
}

上述转换完成的行不起作用。它抛出一个空指针异常。

我试过了:

bool AssignNums(Motor m)
{
   Title t = new Title();
    t  = (Title)m; // Doesn't work.
   //Do other things with "t"
}

上面的方法也行不通。

来自 C++ 背景,有点难以理解 C# 中的强制转换是如何工作的。

在 C++ 中,下面的代码可以工作。

bool AssignNums(Motor* m)
{
   Title* t = (Title*)m; //Will work
   //Do other things with "t"
}

如何在 C# 中做到这一点?没有大量代码来实现“反射”或类似的东西......

【问题讨论】:

  • C# 中有两种强制转换语法。试试另一个:if (m is Title t) { /* code */ }。在您的代码中,您知道mTitle,并且您有一个非空变量将其引用为Title
  • 听起来你在调用它时将null 传递给AssignNums。正如@LeeTaylor 建议的那样,您能否编辑您的问题以显示您如何称呼AssignNums
  • @LeeTaylor 我已经更新了如何调用 AssignNums 的问题。
  • 出现异常的原因是您将Motor 传递给AssignNums,然后将其转换为子类Title。该对象不是Title,因此转换失败并抛出。顺便说一句,在 C++ 中,你不应该使用老式的 C 风格转换,你应该使用 dynamic_cast<>reinterpret_cast<> 等。
  • 你的 C++ 代码有未定义的行为;不允许将指向基类实例的指针转换为指向派生类的指针,除非它真的派生类的实例! C# 的全部意义在于,它将你糟糕的 C++ 代码转化为明智的行为:crash the program 是违反规则时的明智行为。

标签: c#


【解决方案1】:

在 C# 中,您不能将对象强制转换为更具体的子类。想象一下如果可能会发生什么:

class Foo : Motor
{
    int z;
}

Foo m = new Foo();

bool AssignNums(Motor m)
{
   Title t = (Title)m;   // Pretend it doens't throw an exception
   //Do other things with "t"
   t.d = 42; // Oopps! The actual object is Foo, not Title, so there is no field called d
             // InvalidCastException is thrown at the line that attempts the cast
}

如果实际类型与所需类型兼容,您可以在运行时对其进行转换:

bool AssignNums(Motor m)
{
   Title t = m as Title;   
   if (t != null)
   {
       // Do stuff with t
   }
}

从 C# 7 开始,您可以使用更短的形式

bool AssignNums(Motor m)
{
   if (m is Title t)
   {
       // Do stuf with t
   }
}

如果处理多个子类,也可以使用switch..when

switch (m)
{
    case Title t when m is Title:
        // Do something with t
        break;
    case Foo f when m is Foo:
        // Do something with f
        break;
}

但请注意,如果您基于运行时存在的子类进行分支,则表明您可能存在设计问题。

【讨论】:

  • 我认为您需要使您对转换的描述更清楚,如果对象是该子类型,您可以转换为子类型。如果你将它转换为不是的类型,那么你会得到一个异常。
  • 另外,为了完整起见,如果您想处理多个子类型,值得展示新的“switch when”语法
  • @KeithNicholas 好主意,添加了它。
  • 另外,添加了第一个示例中的强制转换抛出 InvalidCastException 的注释。感谢您指出这一点。
【解决方案2】:

使用其他 C# 转换语法,并使用最近的 C# 功能(允许立即将变量与转换相关联):

bool AssignNums(Motor m)
{
   if (m is Title t) {
      //Do things with "t"
   }
}

如果这样做,您将忽略传入的m 实例,这些实例不是Title 类型。 is 演员也从不抛出(相关的 as 操作也不抛出)。

【讨论】: