Inheritence and casting
I'm having a basic problem with casting inherited types to a base class. I know that this is not normally possible, ie, you can cast a derived class to its base class but the opposite is not true. Here's an example snippet that I am struggling with:
Let's say I have defined an abstract class to represent a computer PCI card:
public abstract class PciDevice
{
public abstract int GetDeviceId();
public abstract String GetVendorName();
}
and now I create 3 types of inherited classes (devices):
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;开发者_JAVA百科
}
public class Modem : PciDevice
{
public override int GetDeviceId() { return 1234; }
public override String GetVendorName() { return "BoringCorp"; }
public int baudRate;
bool faxEnabled;
}
Now, I define a "slot" inside the computer:
public class PciCardSlot
{
private int slotId;
private int clockFreq;
private PciDevice cardInSlot;
public PciDevice getCard() { return cardInSlot; }
public void setCard(PciDevice card) { cardInSlot = card; }
}
and I have an array of Slots to represent all slots in the computer:
PciCardSlot [] pciSlotsInComputer = new PciCardSlot[6];
then, I define a function to retrieve a PciDevice object, given a slot id:
public PciDevice getInsertedCard(int slotId)
{
return pciSlotsInComputer[slotId].getCard();
}
so far so good. Now, somewhere in the code, I instantiate AudioCard, GraphicsCard and Modem objects and assign them to slots.
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;
Then, I have a function that looks like follows, which is intended to operate on Modem object:
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);
The casting above does not work since I'm trying to cast from a PciDevice object to a Modem object, which is derived from PciDevice.
I've been reading about this and almost everywhere I look, people seem to think that if you need to cast a base class to a member class, you have a bad design. Is my class hierarchy badly designed? Any suggestions? Thanks for reading.
Well, I dont't think there is an intrinsic problem with treating the PciDevices polymorphically. There are lots of instances built into the framework where is necessary to cast an object back to a "context known" type.
However, the BaudRate is a Modem only Property so its definition and logic should reside in that class. There shouldn't be a higher level function with that specific purpose.
In general, your get functions should be get definitions on properties on the owning objects.
In essence you need to know where the Modem(s) are before you try to access the BaudRate.
If for instance, you wanted to update all Modem's BaudRates and the Modem class was well encapsulated you should be able to do something like
void UpdateModemBaudRates(int baudRate)
{
foreach(PciCardSlot slot in pciSlotsInComputer)
{
Modem modem = slot.CardInSlot as Modem;
if(modem != null)
{
modem.BaudRate = baudRate
}
}
}
If that is hard to follow, look up the as
and is
keywords.
Of course, Linq has a more contemporary way of doing this, here is one inspired by Chris's comment.
void UpdateModemBaudRates(int baudRate)
{
pciSlotsInComputer.Select(s => s.CardInSlot).OfType<Modem>().AsParallel().ForAll<Modem>(modem => modem.BaudRate=baudRate);
}
This part does seem to be either copied wrong or not functional:
PciCardSlot [] pciSlotsInComputer = new PciCardSlot[6];
public PciDevice getInsertedCard(int slotId)
{
return pciSlotsInComputer[slotId];
}
You claim to return an object of PciDevice
but it really is of type PciCardSlot
- two totally unrelated classes, so this doesn't compile.
Your cast here:
public setBaudRateForModem(int slotId, int rate)
{
((Modem)getInsertedCard(slotId)).baudRate = rate; // Can not cast!!!
}
is actually valid and will work if the object instance at the specified slot is indeed a Modem
- but you have to make baudRate
public, otherwise you won't be able to access it - better yet make it a public property.
You can cast a derived class to a base class, but you cannot cast one derived type to another derived type. Take two classes B & C, both derived from A.
class B : A {}
class C: A {}
You can then instantiate them:
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.
are you sure you're passing 1 for the slotId?
What happens if you change setBaudRateForModem like so:
public void setBaudRateForModem( int slotId, int rate ) {
PciDevice device = getInsertedCard( slotId );
Modem modem = device as Modem;
if( null != modem )
{
modem.baudRate = rate;
}
}
use the debugger to determine the type of device being returned. is it in fact a Modem type?
I don't understand how this code is comiling. This is trying to convert PciCardSlot to PciDevice....
public PciDevice getInsertedCard(int slotId) {
return pciSlotsInComputer[slotId];
}
Try this, change PciDevice property to public...
public class PciCardSlot
{
private int slotId;
private int clockFreq;
public PciDevice cardInSlot;
public void setCard(PciDevice card)
{
cardInSlot = card;
}
}
then change getInsertedCard too...
public PciDevice getInsertedCard(int slotId)
{
return pciSlotsInComputer[slotId].cardInSlot;
}
Having to cast to a derived type is something that pops up quite regularly with collections. Before generics, all collections were handled this way.
In this case, you're getting a runtime error because the comment I know that slot 1 contains a modem
must be wrong - put a breakpoint on the line and inspect what type it actually is.
My suggestion is that whilst the hierarchy makes sense, you shouldn't be putting setBaudRateForModem
as a method in PciCardSlot
. At the point that you want to call setBaudRateForModem
, you should already be fully aware that you're dealing with a modem - and so why not already have it cast as a modem? It really doesn't make sense to string together multiple calls like "setBaudRateForModem", "setPortForModem", etc - you're duplicating every property of Modem inside of some magic all-knowing class.
Instead, the moment you think you're working with a Modem - cast it to a Modem, and then access it directly. Beats accessing it indirectly as an integer ;).
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";
}
You should check before using if (getInsertedCard(slotId) is Modem).
I think your class hierarchy is alright!
精彩评论