Почему наследование не работает так, как я думаю, оно должно работать? - PullRequest
8 голосов
/ 06 сентября 2008

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

abstract class Animal
{
  public Leg GetLeg() {...}
}

abstract class Leg { }

class Dog : Animal
{
  public override DogLeg Leg() {...}
}

class DogLeg : Leg { }

Это позволило бы любому, использующему класс Dog, автоматически получать DogLegs, и любому, кто использует класс Animal, чтобы получать Legs. Проблема в том, что переопределенная функция должна иметь тот же тип, что и базовый класс, поэтому она не будет компилироваться. Я не понимаю, почему этого не следует делать, поскольку DogLeg неявно преобразуется в Leg. Я знаю, что есть много способов обойти это, но мне более любопытно, почему это невозможно / реализовано в C #.

РЕДАКТИРОВАТЬ : Я немного изменил это, так как на самом деле я использую свойства вместо функций в моем коде.

РЕДАКТИРОВАТЬ : я изменил его обратно на функции, потому что ответ относится только к этой ситуации (ковариация по параметру значения функции набора свойства не должна работать ). Извините за колебания! Я понимаю, что многие ответы кажутся неуместными.

Ответы [ 17 ]

1 голос
/ 06 сентября 2008

Возможно, проще увидеть проблему с примером:

Animal dog = new Dog();
dog.SetLeg(new CatLeg());

Теперь это должно скомпилироваться, если вы скомпилированы с Dog, но мы, вероятно, не хотим такого мутанта.

С этим связан вопрос, должен ли Dog [] быть Animal [] или IList IList ?

0 голосов
/ 16 сентября 2008

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

abstract class Animal<LegType> where LegType : Leg
{
    public abstract LegType GetLeg();
}

abstract class Leg { }

class Dog : Animal<DogLeg>
{
    public override DogLeg GetLeg()
    {
        return new DogLeg();
    }
}

class DogLeg : Leg { }
0 голосов
/ 06 сентября 2008

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

0 голосов
/ 06 сентября 2008

@ Брайан Лихи Очевидно, что если вы работаете с ним только как с ногой, нет необходимости или причины разыгрывать. Но если есть какое-то специфическое поведение DogLeg или Dog, иногда есть причины, по которым приведение является необходимым.

0 голосов
/ 06 сентября 2008

@ Люк

Я думаю, что вы, возможно, неправильно понимаете наследство. Dog.GetLeg () вернет объект DogLeg.

public class Dog{
    public Leg GetLeg(){
         DogLeg dl = new DogLeg(super.GetLeg());
         //set dogleg specific properties
    }
}


    Animal a = getDog();
    Leg l = a.GetLeg();
    l.kick();

фактический вызванный метод будет Dog.GetLeg (); и DogLeg.Kick () (я предполагаю, что там существует метод Leg.kick ()), объявленный тип возврата DogLeg не нужен, потому что это то, что возвращается, даже если тип возврата для Dog.GetLeg () нога.

0 голосов
/ 06 сентября 2008

Важно помнить, что вы можете использовать производный тип в каждом месте, где вы используете базовый тип (вы можете передать Dog любому методу / свойству / полю / переменной, ожидающему Animal)

Давайте возьмем эту функцию:

public void AddLeg(Animal a)
{
   a.Leg = new Leg();
}

Совершенно допустимая функция, теперь давайте вызовем такую ​​функцию:

AddLeg(new Dog());

Если свойство Dog.Leg не относится к типу Leg, функция AddLeg неожиданно содержит ошибку и не может быть скомпилирована.

0 голосов
/ 06 сентября 2008

Вы также можете вернуть интерфейс ILeg, который реализован в Leg и / или DogLeg.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...