Проблема наследования C # Generics - PullRequest
12 голосов
/ 06 мая 2011

Я хотел бы добавить различные типы объектов, производных от одного класса с обобщениями, в Список базового типа.Я получаю эту ошибку компиляции

Error   2   Argument 1: cannot convert from 'ConsoleApplication1.Stable' to 'ConsoleApplication1.ShelterBase<ConsoleApplication1.AnimalBase>'   C:\Users\ysn\Desktop\ConsoleApplication1\ConsoleApplication1\Program.cs 43  26  ConsoleApplication1

Я не вижу проблемы, не могли бы вы предоставить мне альтернативный способ делать такие вещи?

abstract class AnimalBase { public int SomeCommonProperty;}

abstract class ShelterBase<T> where T : AnimalBase
{
    public abstract List<T> GetAnimals();
    public abstract void FeedAnimals(List<T> animals);
}


class Horse : AnimalBase { }

class Stable : ShelterBase<Horse>
{
    public override List<Horse> GetAnimals()
    {
        return new List<Horse>();
    }

    public override void FeedAnimals(List<Horse> animals)
    {
        // feed them
    }
}


class Duck : AnimalBase { }

class HenHouse : ShelterBase<Duck>
{
    public override List<Duck> GetAnimals()
    {
        return new List<Duck>();
    }

    public override void FeedAnimals(List<Duck> animals)
    {
        // feed them
    }
}

class Program
{
    static void Main(string[] args)
    {
        List<ShelterBase<AnimalBase>> shelters = new List<ShelterBase<AnimalBase>>();

        ///////////////////////////// following two lines do not compile
        shelters.Add(new Stable()); 
        shelters.Add(new HenHouse());
        /////////////////////////////

        foreach (var shelter in shelters)
        {
            var animals = shelter.GetAnimals();
            // do sth with 'animals' collection
        }
    }
}

Ответы [ 5 ]

19 голосов
/ 06 мая 2011

Вы можете использовать Контравариантность , но только , если вы измените свой абстрактный класс на интерфейс и тип возврата GetAnimals на IEnumerable<T>, потому что List<T> нене поддерживает эту функцию.

Код, который работает:

abstract class AnimalBase { public int SomeCommonProperty;}

interface IShelterBase<out T> where T : AnimalBase
{
    IEnumerable<T> GetAnimals();
}

class Horse : AnimalBase { }

class Stable : IShelterBase<Horse>
{
    public IEnumerable<Horse> GetAnimals()
    {
        return new List<Horse>();
    }
}

class Duck : AnimalBase { }

class HenHouse : IShelterBase<Duck>
{
    public IEnumerable<Duck> GetAnimals()
    {
        return new List<Duck>();
    }
}

void Main()
{
    List<IShelterBase<AnimalBase>> shelters = new List<IShelterBase<AnimalBase>>();

    shelters.Add(new Stable());
    shelters.Add(new HenHouse());

    foreach (var shelter in shelters)
    {
        var animals = shelter.GetAnimals();
        // do something with 'animals' collection
    }
}
2 голосов
/ 06 мая 2011

Вы можете сделать это, используя ковариацию и контравариантность, но ваш класс ShelterBase должен быть производным от интерфейса, поскольку только интерфейсы могут быть ко-или контравариантными. Ваш список должен быть List<IShelterBase<T>>, и он должен работать.

Подробнее см. Здесь

1 голос
/ 09 мая 2011

Чтобы решить эту конкретную проблему, вам на самом деле не нужна ковариация.Когда вы используете списки животных, вы все равно получаете интерфейс AnimalBase через IShelterBase<out T>.Можно также выставить список AnimalBase через базовый класс.

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

abstract class ShelterBase<T> where T : AnimalBase
{
    public List<AnimalBase> GetAnimals(){return new List<AnimalBase>();}
}

class Stable : ShelterBase<Horse>
{
    public List<Horse> GetHorses(){return new List<Horse>();}
}

Еще одна проблема с этим дизайном - выставить коллекцию через самый производный тип - то есть List, в отличие от IEnumerable или IList.Поскольку вы столкнулись с проблемой создания класса, абстрагирующего коллекцию животных, вы должны действительно защитить внутреннюю коллекцию, запретив прямую вставку / удаление.Как только вы это сделаете, все станет немного проще.Например, вот как я бы решил эту проблему.

abstract class ShelterBase<T> where T : AnimalBase
{
    protected List<T> _Animals;
    public AnimalBase() {
       _Animals = CreateAnimalCollection();
    }

    protected abstract List<T> CreateAnimalCollection();
    public IEnumerable<AnimalBase> GetAnimals(){return _Animals.Cast<AnimalBase>();}

    //Add remove operations go here
    public void Add(T animal){_Animals.Add(animal);}
    public void Remove(T animal){_Animals.Remove(animal);}

}

class Stable : ShelterBase<Horse>
{
    protected override List<Horse> CreateAnimalCollection(){return new List<Horse>();}

    public IEnumerable<Horse> GetHorses(){return _Animals;}
}

Вы заметите, что внутренняя коллекция животных никогда не отображается в виде изменяемого списка.Это хорошо, так как позволяет вам лучше контролировать его содержимое.Методы Add и Remove в базовом укрытии в этом примере немного надуманы, так как они не добавляют ничего лишнего по сравнению с прямым доступом к сбору, но вы можете добавить туда логику - например, проверку максимального размера укрытия при добавлении или проверкевозраст животных до его удаления.

1 голос
/ 06 мая 2011

Статья о совместной и противоречивой дисперсии в дженериках:

http://msdn.microsoft.com/en-us/library/dd799517.aspx

0 голосов
/ 14 июля 2015

У меня просто похожая проблема. Все мои сущности происходят из базового класса, и я создал метод для возврата составного списка идентификаторов. Так что создал метод с обобщениями и получил некоторые ошибки преобразования. Удалось преобразовать универсальный T в Object и Object в BaseClass и поставить некоторую проверку на всякий случай.

//Needs it generic to use in a lot of derived classes
private String ConcatIds<T>(List<T> listObj)
{
    String ids = String.Empty;

    foreach (T obj in listObj)
    {
        BaseEntity be = CastBack(obj);

        if (ids.Count() > 0)
            ids = ids + String.Format(", {0}", be.Id);
        else
            ids = be.Id.ToString();
    }

    return ids;
}

//I'll probably move it to the Base Class itself
private BaseEntity CastBack<T>(T genericObj)
{
    Type type = typeof(T);

    if (type.BaseType == typeof(BaseEntity))
    {
        Object _obj = (Object)genericObj;
        return (BaseEntity)_obj;
    }
    else
    {
        throw new InvalidOperationException(String.Format("Cannot convert {0} to BaseEntity", type.ToString()));
    }
}

использование:

public class BaseEntity
{
   public Int32 Id {get; set;}
}

public class AnyDerivedClass : BaseEntity
{
  // Lorem Ipsum
}

private void DoAnything(List<AnyDerivedClass> myList)
{
    String ids = this.ConcatIds<AnyDerivedClass>(myList);
}

Редактировать: Через некоторое время мне нужно было создать дополнительный уровень иерархии и иногда приводить обратно к родителю или к прародителю. Поэтому я позволил своему методу CastBack более универсальным.

private B CastBack<T,B>(T genericObj)
{
    Type type = typeof(T);

    if (type.BaseType == typeof(B))
    {
        Object _obj = (Object)genericObj;
        return (B)_obj;
    }
    else
    {
        throw new InvalidOperationException(String.Format("Cannot cast back {0}", type.ToString()));
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...