我在将继承类型转换为基类时遇到了一个基本问题。我知道这通常是不可能的,也就是说,你可以将派生类强制转换为它的基类,但事实并非如此。这是我正在努力解决的一个示例片段:
假设我已经定义了一个抽象类来表示计算机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对象。
我一直在阅读这个以及我看的几乎所有地方,人们似乎认为如果你需要将基类强制转换为成员类,那么你的设计就会很糟糕。我的班级层次结构设计得很糟糕吗?有什么建议?谢谢你的阅读。
答案 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
}
}
}
如果难以理解,请查看as
和is
个关键字。
当然,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).
我认为你的班级层次结构没问题!