继承和铸造

时间:2011-04-08 13:24:54

标签: c# .net inheritance casting polymorphism

我在将继承类型转换为基类时遇到了一个基本问题。我知道这通常是不可能的,也就是说,你可以将派生类强制转换为它的基类,但事实并非如此。这是我正在努力解决的一个示例片段:

假设我已经定义了一个抽象类来表示计算机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; }
}

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

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对象。

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

7 个答案:

答案 0 :(得分:7)

好吧,我不认为以多态方式处理PciDevices存在内在问题。框架中内置了许多实例,用于将对象转换回“已知上下文”类型。

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

通常,get函数应该在拥有对象的属性上获取定义。

实际上,在尝试访问BaudRate之前,您需要知道调制解调器的位置。

例如,如果您想要更新所有调制解调器的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);
}

答案 1 :(得分:4)

这部分看起来似乎被复制错误或无效:

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,否则您将无法访问它 - 更好但把它变成公共财产。

答案 2 :(得分:1)

您可以将派生类强制转换为基类,但不能将一个派生类型强制转换为另一个派生类型。参加两门课程B&amp; 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.

答案 3 :(得分:1)

你确定你为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;
  }
}

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

答案 4 :(得分:1)

我不明白这段代码是如何编写的。这是为了将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;
        }

答案 5 :(得分:0)

必须使用集合来定期弹出派生类型。在仿制药之前,所有的收藏都是以这种方式处理的。

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

我的建议是,虽然层次结构很有意义,但您不应该将setBaudRateForModem作为PciCardSlot中的方法。在您想要调用setBaudRateForModem时,您应该已经完全意识到您正在处理调制解调器 - 那么为什么不将它作为调制解调器投射呢?将“setBaudRateForModem”,“setPortForModem”等多个调用串在一起真的没有意义 - 你在一些神奇的全知道类中复制了Modem的每个属性。

相反,您认为自己正在使用调制解调器的那一刻 - 将其转换为调制解调器,然后直接访问它。节拍间接作为整数访问它;)。

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

答案 6 :(得分:0)

您应该在使用if (getInsertedCard(slotId) is Modem).

之前进行检查

我认为你的班级层次结构没问题!