Проблема с наследованием дженериков С#
Я хотел бы добавить различные типы объектов, полученных из одного класса с generics, в список базового типа. Я получаю эту ошибку компиляции
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
}
}
}
Ответы
Ответ 1
Вы можете использовать contravariance, но только если вы измените свой абстрактный класс на интерфейс и возвращаемый тип 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
Вы можете сделать это, используя ковариацию и контравариантность, но ваш класс ShelterBase должен вытекать из интерфейса, так как только интерфейсы могут быть ковариантными или контравариантными. Вам нужно будет List<IShelterBase<T>>
, и он должен работать.
Подробнее см. здесь
Ответ 3
Статья о со и противоречие в дженериках:
http://msdn.microsoft.com/en-us/library/dd799517.aspx
Ответ 4
Чтобы решить эту конкретную проблему, на самом деле вам не нужна ковариация. Когда вы потребляете списки животных, вы по-прежнему получаете 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
в базовом приюте немного придуманы в этом примере, поскольку они не добавляют ничего лишнего через прямой доступ к коллекции, но вы можете добавить туда логику, например. проверяя максимальный размер приюта при добавлении или проверке животного возраста перед его удалением.
Ответ 5
У меня была аналогичная проблема.
Все мои объекты производятся из базового класса, и я создал метод для возврата объединенного списка идентификаторов. Так был создан метод с generics и получил некоторые ошибки преобразования. Удалось применить общий 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()));
}
}