C # Полиморфизм - Доступ к свойствам дочернего класса, которых нет в родительском - PullRequest
9 голосов
/ 31 января 2011

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

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 ]

18 голосов
/ 31 января 2011

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

Как вы наверняка видели в других ответах, вы можете получить доступ к свойству 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!

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

11 голосов
/ 31 января 2011

Итак, в этом элементарном примере у меня был бы список Существ, и я должен был бы иметь возможность вызывать из него свойство Маны любого данного Магического существа.

Да, это возможно. Используйте 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);
}
5 голосов
/ 31 января 2011

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

Creature aCreature = GetMyCreature();
Magical asMagical = aCreature as Magical; // Using "as" operator..
if (asMagical != null)
    Console.WriteLine(asMagical.Mana);
1 голос
/ 31 января 2011
if (myCreature is Magical)
{
     var myMagicalCreature = (Magical)myCreature;
     //....access mana, etc.
}
1 голос
/ 31 января 2011

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

((Magical)myCreature).Mana = 0;
0 голосов
/ 31 января 2011

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

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

//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;
0 голосов
/ 31 января 2011

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

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 - вам все равно, потому что вы относитесь к ним полиморфно.

0 голосов
/ 31 января 2011

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

foreach (var item in collection)
{
    Magical magical = item as Magical;
    if (magical != null)
        magical.Mana = 10;
}       
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...