Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Generics Inheritance Problem

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
        }
    }
}
like image 273
Yasin Kilicdere Avatar asked May 06 '11 09:05

Yasin Kilicdere


2 Answers

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.

like image 60
Igor Zevaka Avatar answered Nov 04 '22 04:11

Igor Zevaka


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

like image 24
Tim Rogers Avatar answered Nov 04 '22 04:11

Tim Rogers