Наследование и приведение

#c# #.net #inheritance #casting #polymorphism

#c# #.net #наследование #Кастинг #полиморфизм

Вопрос:

У меня возникла основная проблема с приведением унаследованных типов к базовому классу. Я знаю, что обычно это невозможно, т. Е. вы можете привести производный класс к его базовому классу, но обратное неверно. Вот пример фрагмента, с которым я борюсь:

Допустим, я определил абстрактный класс для представления компьютерной 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 с заданным идентификатором слота:

 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 в объект Modem, который является производным от PciDevice.

Я читал об этом, и почти везде, куда бы я ни посмотрел, люди, похоже, думают, что если вам нужно привести базовый класс к классу-члену, у вас плохой дизайн. Плохо ли спроектирована моя иерархия классов? Есть предложения? Спасибо за чтение.

Комментарии:

1. Что Can not cast!!! на самом деле означает? При условии, что вы показали большую часть своего кода, иерархия наследования выглядит разумной, и, скорее всего, проблема в том, что вы приводите что-то, что не является Modem для Modem a. Вы отлаживали свой код?

2. Это выглядит нормально. Вы получаете ошибку во время компиляции или во время выполнения? Если это ошибка времени выполнения, то тип, который вы пытаетесь привести, вероятно, не является «Модемом».

Ответ №1:

Ну, я не думаю, что существует внутренняя проблема с полиморфной обработкой PciDevices. В фреймворк встроено множество экземпляров, где необходимо привести объект обратно к типу, известному в контексте.

Однако BaudRate является свойством только для модема, поэтому его определение и логика должны находиться в этом классе. Не должно быть функции более высокого уровня с этой конкретной целью.

В общем, ваши функции get должны представлять собой определения get для свойств объектов-владельцев.

По сути, вам нужно знать, где находятся модемы, прежде чем пытаться получить доступ к скорости передачи данных.

Например, если вы хотели обновить все скорости передачи данных модема, а класс Modem был хорошо инкапсулирован, вы должны быть в состоянии сделать что-то вроде

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

Если это трудно понять, найдите ключевые слова as и is .

Конечно, у Linq есть более современный способ сделать это, вот один из них, вдохновленный комментарием Криса.

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

Комментарии:

1. Это нравится больше, чем мой ответ ;). Лучший пример того, что я пытался сказать.

2. И снова случай, когда Linq упрощает жизнь: var modems = pciSlotsInComputer.Select(s => s.CardInSlot).OfType<Modem>();

3. Полностью согласен, 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 общедоступным, иначе вы не сможете получить к нему доступ — а еще лучше сделайте его общедоступным свойством.

Комментарии:

1. Спасибо, вы правы, я отредактировал код, чтобы добавить функцию getCard() в PciCardSlot и обновил функцию getInsertedCard. Извините за ошибку, код на самом деле довольно большой, поэтому, когда я пытался его сократить, я пропустил несколько строк. С уважением,

Ответ №3:

Вы можете привести производный класс к базовому классу, но вы не можете привести один производный тип к другому производному типу. Возьмем два класса 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.
  

Ответ №4:

вы уверены, что передаете 1 для идентификатора slotId?

Что произойдет, если вы измените setBaudRateForModem следующим образом:

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

используйте отладчик для определения типа возвращаемого устройства. действительно ли это тип Modem?

Ответ №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;
        }
  

Комментарии:

1. Да, моя ошибка, извините. Исходный тестовый код содержит намного больше строк; когда я пытался сократить его перед публикацией в SO, я пропустил некоторые. С уважением

2. Итак, вы заставили это работать? Я только что настроил тестовый проект, и он отлично сработал.

Ответ №6:

Необходимость приведения к производному типу — это то, что довольно регулярно всплывает в коллекциях. До создания generics все коллекции обрабатывались таким образом.

В этом случае вы получаете ошибку времени выполнения, потому что комментарий 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";
}
  

Ответ №7:

Перед использованием следует проверить if (getInsertedCard(slotId) is Modem).

Я думаю, что ваша иерархия классов в порядке!