I'd like to add different types of objects derived from one class with generics into a List of base type. I get this compile error
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
I can't see the problem could you provide me an alternative way of doing this kind of thing?
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
}
}
}
To solve this particular problem you don't actually need covariance. When you consume animal lists, you still get AnimalBase
through IShelterBase<out T>
interface. Might as well expose a list of AnimalBase
through the base class.
A better and cleaner design would be to make GetAnimals
return a list of AnimalBase
, and to create an overload in each shelter class to return a list of that particular animal.
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>();}
}
Another problem with this design is exposing a collection through the most derived type - i.e. List
, as opposed to IEnumerable
or IList
. Seeing as you went into trouble of creating a class abstracting a collection of animals, you should really protect the inner collection by disallowing direct insert/delete ability. Once you do that, things become a bit easier. E.g. here is how I would solve this problem.
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;}
}
You will notice that the internal collection of animals is never exposed as mutable list. This is good, as it allows you tighter control over its contents. The Add
and Remove
methods in the base shelter are a bit contrived in this example as they don't add anything extra over direct collection access, but you can add logic there - e.g. checking for maximum shelter size when adding or checking the animal age before removing it.
You can do this using covariance and contravariance, but your ShelterBase class needs to derive from an interface, since only interfaces can be co- or contravariant. You list will need to be a List<IShelterBase<T>>
and it should work.
See here for more info
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With