【问题标题】:Inheritence and casting继承和铸造
【发布时间】:2011-08-01 12:41:13
【问题描述】:

我在将继承的类型转换为基类时遇到了一个基本问题。我知道这通常是不可能的,也就是说,您可以将派生类强制转换为其基类,但反之则不然。这是我正在努力解决的一个示例 sn-p:

假设我定义了一个抽象类来表示计算机 PCI 卡:

public abstract class PciDevice
{
   public abstract int GetDeviceId();
   public abstract String GetVendorName();
}

现在我创建了 3 种类型的继承类(设备):

public class GraphicsCard : PciDevice
{
   public override int GetDeviceId() { return 1666; }
   public override String GetVendorName() { return "CoolCorp"; }

   int trianglesPerSecond;
   ChipsetTypeEnum chipsetType;
}

public class AudioCard : PciDevice
{
   public override int GetDeviceId() { return 1999; }
   public override String GetVendorName() { return "ShinyCorp"; }

   int numChannels;
   int samplingRate;
}

public class Modem : PciDevice
{
   public override int GetDeviceId() { return 1234; }
   public override String GetVendorName() { return "BoringCorp"; }

   public int baudRate;
   bool faxEnabled;
}

现在,我在计算机内部定义了一个“插槽”:

public class PciCardSlot
{
   private int slotId;
   private int clockFreq;
   private PciDevice cardInSlot;

   public PciDevice getCard() { return cardInSlot; }

   public void setCard(PciDevice card) { cardInSlot = card; }
}

我有一个插槽数组来表示计算机中的所有插槽:

PciCardSlot [] pciSlotsInComputer = new PciCardSlot[6];

然后,我定义了一个函数来检索一个 PciDevice 对象,给定一个插槽 id:

public PciDevice getInsertedCard(int slotId)
{
   return pciSlotsInComputer[slotId].getCard();
}

到目前为止一切顺利。现在,在代码的某处,我实例化了 AudioCard、GraphicsCard 和 Modem 对象并将它们分配给插槽。

AudioCard a = new AudioCard();
Modem b = new Modem();
GraphicsCard c = new GraphicsCard();
PciCardSlot s0 = new PciCardSlot(); s0.setCard(a);
PciCardSlot s1 = new PciCardSlot(); s1.setCard(b);
PciCardSlot s2 = new PciCardSlot(); s2.setCard(c);
pciSlotsInComputer[0] = s0; pciSlotsInComputer[1] = s1; pciSlotsInComputer[2] = s2;

然后,我有一个如下所示的函数,用于对 Modem 对象进行操作:

public setBaudRateForModem(int slotId, int rate)
{
   ((Modem)getInsertedCard(slotId)).baudRate = rate; // Can not cast!!!
}
...
// I know that slot 1 contains a modem, so I do:
setBaudRateForModem(1, 9600);

上面的转换不起作用,因为我试图从 PciDevice 对象转换为从 PciDevice 派生的 Modem 对象。

我一直在阅读这方面的内容,几乎在我所看到的所有地方,人们似乎都认为,如果您需要将基类强制转换为成员类,那么您的设计就很糟糕。我的班级层次结构设计得很糟糕吗?有什么建议?感谢阅读。

【问题讨论】:

  • Can not cast!!! 究竟是什么意思?如果您已经展示了大部分代码,继承层次结构是合理的,并且很可能问题是您将不是Modem 的东西转换为Modem。你调试你的代码了吗?
  • 这看起来不错。您是否收到编译时错误或运行时错误?如果是运行时错误,那么您尝试转换的类型可能不是“调制解调器”。

标签: c# .net inheritance casting polymorphism


【解决方案1】:

嗯,我不认为多态处理 PciDevices 存在内在问题。框架中内置了许多实例,需要将对象转换回“上下文已知”类型。

但是,BaudRate 是仅限调制解调器的属性,因此它的定义和逻辑应该驻留在该类中。不应该有更高级别的功能用于特定目的。

一般来说,您的 get 函数应该是 get 对拥有对象的属性的定义。

本质上,在您尝试访问波特率之前,您需要知道调制解调器在哪里。

例如,如果您想更新所有调制解调器的 BaudRates 并且调制解调器类被很好地封装,您应该能够执行类似的操作

void UpdateModemBaudRates(int baudRate)
{
    foreach(PciCardSlot slot in pciSlotsInComputer)
    {
        Modem modem = slot.CardInSlot as Modem;
        if(modem != null)
        {
            modem.BaudRate = baudRate
        }
    }
}

如果难以理解,请查找 asis 关键字。

当然,Linq 有一种更现代的方式来做到这一点,这是受 Chris 评论启发的一种方式。

void UpdateModemBaudRates(int baudRate)
{
    pciSlotsInComputer.Select(s => s.CardInSlot).OfType<Modem>().AsParallel().ForAll<Modem>(modem => modem.BaudRate=baudRate);
}

【讨论】:

  • 比我的回答更喜欢这个 ;)。我想说的更好的例子。
  • 又是一个 Linq 让生活变得更简单的案例:var modems = pciSlotsInComputer.Select(s =&gt; s.CardInSlot).OfType&lt;Modem&gt;();
  • 完全同意 Linq 是一种新的更好的方法,但不确定它是否能帮助每个人理解。
【解决方案2】:

这部分似乎是复制错误或不起作用:

PciCardSlot [] pciSlotsInComputer = new PciCardSlot[6];

public PciDevice getInsertedCard(int slotId) 
{
   return pciSlotsInComputer[slotId];
}

您声称返回了一个 PciDevice 的对象,但它确实是 PciCardSlot 类型的 - 两个完全不相关的类,所以这不会编译。

你的演员在这里:

public setBaudRateForModem(int slotId, int rate) 
{
   ((Modem)getInsertedCard(slotId)).baudRate = rate; // Can not cast!!!
}

实际上是有效的,并且如果指定插槽中的对象实例确实是 Modem 则将起作用 - 但您必须将 baudRate 公开,否则您将无法访问它 - 最好将其设为公共财产。

【讨论】:

  • 谢谢,你是对的,我编辑了代码,将 getCard() 函数添加到 PciCardSlot 并更新了 getInsertedCard 函数。很抱歉这个错误,代码实际上相当大,所以当我试图修剪它时,我错过了一些行。问候,
【解决方案3】:

您可以将派生类强制转换为基类,但不能将一种派生类型强制转换为另一种派生类型。取两个类 B 和 C,均派生自 A。

class B : A {}

class C: A {}

然后您可以实例化它们:

B object1 = new B();
C object2 = new C();

A base1 = (A)object1; // Casting to base.
A base2 = (A)object2; // Casting to base.

C newObect = (C)object1; // Fail. You cannot cast B to C as they are different classes.

【讨论】:

    【解决方案4】:

    您确定要为 slotId 传递 1 吗?

    如果您像这样更改 setBaudRateForModem 会发生什么:

    public void setBaudRateForModem( int slotId, int rate ) {
      PciDevice device = getInsertedCard( slotId );
      Modem modem = device as Modem;
      if( null != modem )
      {
        modem.baudRate = rate;
      }
    }
    

    使用调试器来确定返回的设备类型。它实际上是调制解调器类型吗?

    【讨论】:

      【解决方案5】:

      我不明白这段代码是如何编译的。这是试图将 PciCardSlot 转换为 PciDevice....

      public PciDevice getInsertedCard(int slotId) {
         return pciSlotsInComputer[slotId];
      }
      

      试试这个,将 PciDevice 属性更改为 public...

      public class PciCardSlot
          {
              private int slotId;
              private int clockFreq;
              public PciDevice cardInSlot;
              public void setCard(PciDevice card)
              {
                  cardInSlot = card;
              }
          }
      

      然后也更改 getInsertedCard...

      public PciDevice getInsertedCard(int slotId)
              {
                  return pciSlotsInComputer[slotId].cardInSlot;
              }
      

      【讨论】:

      • 是的,我的错误,对不起。原始测试代码有更多行;当我在发布到 SO 之前尝试修剪它时,我错过了一些。问候
      • 所以你让它工作了吗?我刚刚设置了一个测试项目,它运行良好。
      【解决方案6】:

      必须强制转换为派生类型是经常出现在集合中的事情。在泛型之前,所有的集合都是这样处理的。

      在这种情况下,您会遇到运行时错误,因为注释 I know that slot 1 contains a modem 一定是错误的 - 在行上放置一个断点并检查它实际上是什么类型。

      我的建议是,虽然层次结构有意义,但您不应该将setBaudRateForModem 作为PciCardSlot 中的方法。在您想调用setBaudRateForModem 时,您应该已经完全意识到您正在处理调制解调器 - 那么为什么不将其转换为调制解调器呢?将多个调用(如“setBaudRateForModem”、“setPortForModem”等)串在一起确实没有任何意义 - 您在某个神奇的无所不知的类中复制了调制解调器的每个属性。

      相反,当您认为自己正在使用调制解调器时 - 将其转换为调制解调器,然后直接访问它。优于以整数形式间接访问它;)。

      void ProcessModem(int slot)
      {
        Modem modem = getInsertedCard(slot) as Modem;
        if (modem == null) throw new ArgumentException("slot is not a modem!");
        modem.BaudRate = 9600;
        modem.Port = "COM5";
      }
      

      【讨论】:

        【解决方案7】:

        您应该在使用if (getInsertedCard(slotId) is Modem).之前检查

        我觉得你的类层次没问题!

        【讨论】:

          猜你喜欢
          • 2021-06-14
          • 2013-01-25
          • 1970-01-01
          • 1970-01-01
          • 2018-05-12
          • 1970-01-01
          • 1970-01-01
          • 2020-01-18
          • 1970-01-01
          相关资源
          最近更新 更多