C# полиморфизм-доступ к свойствам дочернего класса, которые не находятся в Родительском

У меня возникают проблемы с возможностью вызова функций / свойств, уникальных для дочернего класса. Мне интересно, есть ли способ сделать что-то похожее на следующее:

public class Creature
{
   public int HealthPoints {get; set;}
   public string Name {get; set;}
   public int AttackValue {get; set;}

   public Creature();
}

public class Magical : Creature
{
   public int Mana {get; set;}
}

8 ответов


короткий ответ:

как вы наверняка видели в других ответов, вы можете получить доступ к Mana свойство магических существ следующим образом:

foreach (Creature creature in creatures)
{
    if (creature is Magical magical)
    {
        // ... do something with 'magical.Mana' here...
    }
}

Идем дальше:

поскольку мы уже говорим о полиморфизме, вы должны знать, что if (x is SomeType) { .. } может быть код запах. Допустим, у вас действительно есть foreach цикл для обработки всех ваших существ. Тогда может быть лучше создать виртуальный метод в базовом классе и делать все, что вы делаете внутри foreach цикл в этом виртуальном методе. Затем вы переопределите метод в своем Magical класса и тем самым избавиться от if заявление.

давайте сделаем пример. Допустим, все ваши существа получают удар и теряют энергию. Нормальные существа теряют очки здоровья, в то время как магические существа теряют Ману:

public partial class Creature   // rest of your class is omitted in this example
{
    public virtual void TakeHit()
    {
        // whatever you had in your original foreach loop, e.g.:
        HealthPoints--;
    }
}

public partial class Magical : Creature
{
    public override void TakeHit()
    {
        // ... any special processing for Magical creatures goes here, e.g.:
        Mana--;
        // (you could also call 'base.TakeHit()' in this method.)
    }
}

ваш оригинальный foreach петли теперь становится намного проще:

foreach (Creature creature in creatures)
{                        // note: no more if statement -- The virtual method
    creature.TakeHit();  // invocation now takes care of distinguising
}                        // different (sub-)types of Creatures!

я хотел бы рекомендовать очень хорошая презентация на эту тему для вас: обусловленность и полиморфизм.


Да, это возможно. Использовать Enumerable.OfType<T>:

IEnumerable<Creature> creatures = new List<Creature>();
// populate creatures
var magicalCreatures = creatures.OfType<Magical>();
foreach(var magicalCreature in magicalCreatures) {
    Console.WriteLine(magicalCreature.Mana);
}

вам нужно сделать преобразование в "Волшебный" в этот момент. Например:

Creature aCreature = GetMyCreature();
Magical asMagical = aCreature as Magical; // Using "as" operator..
if (asMagical != null)
    Console.WriteLine(asMagical.Mana);

в этом конкретном примере вам просто нужно будет бросить существо как магическое, а затем вызвать свою функцию. Затем вы бы сделали что-то вроде:

((Magical)myCreature).Mana = 0;

if (myCreature is Magical)
{
     var myMagicalCreature = (Magical)myCreature;
     //....access mana, etc.
}

это должно сделать работу:

foreach (var item in collection)
{
    Magical magical = item as Magical;
    if (magical != null)
        magical.Mana = 10;
}       

если свойство уникально для дочернего класса, вам нужно будет привести его в дочерний класс для доступа к свойству, например

foreach(Creature creature in creatures)
{
    Magical magical = creature as Magical;
    if (magical != null)
    {
        // Do something with magical.Mana.
    }
}

или альтернативно с LINQ:

foreach(Magical magical in creatures.OfType<Magical>())
{
    // Do something with magical.Mana.
}

нет смысла делать что-то вроде:

foreach(Creature creature in creatures)
{
    // Do something with creature.Mana.
}

потому что ваше существо не может быть магическим и, следовательно, не будет иметь маны.

полиморфизм приходит, когда у вас есть две разные вещи с одинаковыми свойствами.

foreach(Creature creature in creatures)
{
    // Do something with creature.AttackValue.
}

AttackValue является общим как для существ, так и для магических, поэтому вы можете с удовольствием использовать его, не заботясь о том, является ли это существо или магическим. И у них могут быть совершенно разные реализации AttackValue - вам все равно, потому что вы относитесь к ним полиморфно.


наследование работает только в одну сторону; все дочерние классы являются их родителями, но ни один родитель не является их ребенком.

чтобы получить то, что вы хотите, вы должны знать, что класс, который вы рассматриваете как существо, на самом деле волшебный. Вы можете проверить это, используя:

//Uses reflection to examine the type; useful in generic-typed situations
bool isMagical = typeof(T).IsAssignableFrom(Magical); //generic to T where T:Creature

//A keyword-based statement that basically does the same thing given an instance
bool isMagical = creatureInstance is Magical;

Если это возможно, вы могли бы предоставить не отражающий способ обнаружить это, объявив что-то в существе, которое должно быть указано в магическом:

public Class Creature
{
   public int HealthPoints {get; set;}
   public string Name {get; set;}
   public int AttackValue {get; set;}
   public abstract CreatureType Type {get;}

   public Creature();
}

public Class Magical : Creature
{
   public override CreatureType Type {get{return CreatureType.Magical;}}
   public int Mana {get; set;}
}

...

if(creatureInstance.Type == CreatureType.Magical) {...}

как только вы знаете, что существо является магическим, вы можете рассматривать экземпляр как его подкласс, бросая:

//Direct cast; throws an InvalidCastException if creatureInstance isn't Magical
var mana = ((Magical)creatureInstance).Mana;

//Safe cast - returns null if creatureInstance isn't Magical,
//which in this usage will throw a NullReferenceException anyway
var mana = (creatureInstance as Magical).Mana;