Search code examples
c#arraysgenericsinheritanceinvariants

Array of different generics


Long story short, I would like to be able to store generics using different type parameters in an array, by using a parent type to all the types used. MSDN mentioned it was impossible, as generics were invariant types, but a comment stated that this changed since the 4.0 framework.

Here is a basic example of what I would like to do:

    public class Animal
    {
    }
    public class Dog : Animal
    {
    }
    public class Cat : Animal
    {
    }

    public class MyGeneric<T>
    { }
    public class MyInheritedGeneric<T> : MyGeneric<T>
    { }

    static void Main(string[] args)
    {
        MyGeneric<Animal>[] myGenericArray = new MyGeneric<Animal>[] 
        {
            new MyGeneric<Dog>(),
            new MyInheritedGeneric<Cat>()
        };
    }

This returns the similar errors:

Cannot implicitly convert type
'InheritanceTest.Program.MyGeneric<InheritanceTest.Program.Dog>' to
'InheritanceTest.Program.MyGeneric<InheritanceTest.Program.Animal>'

Cannot implicitly convert type
'InheritanceTest.Program.MyInheritedGeneric<InheritanceTest.Program.Cat>'
to 'InheritanceTest.Program.MyGeneric<InheritanceTest.Program.Animal>'

Is there any way to store generics in an array using the parent class of the type, or is this simply impossible? I really hope it is possible, otherwise it will make my program a nightmare...

EDIT: A bit more context!

I am making classes to generate enemies in a game. I call them Templates (nothing to do with actual template classes, I could very well have called them Blueprints or Factories). An enemy constructor takes in a Template, which it uses to determine its own values. When the game loads, the templates are used to generate all enemies, using their Generate() function, which returns an array of the corresponding type they are assigned to produce. All the objects to be created with a template are to have a constructor taking a template as their sole parameter.

public class Template<T>
{
    protected static Random random = new Random(); 
    protected int _amount;

    public int Amount
    {
        get { return _amount; }
    }

    public virtual T CreateInstance()
    {
        return (T)Activator.CreateInstance(typeof(T), this);
    }
    public virtual T[] Generate()
    {
        T[] objects = new T[Amount];
        for (int i = 0; i < Amount; ++i)
            objects[i] = CreateInstance();
        return objects;
    }
}

Here is a summary of the BasicZombie.cs file, which contains the actual enemy class and the template.

    class Tpl_BasicZombie : Tpl_Enemy<BasicZombie>
{
    public Tpl_BasicZombie()
    {
        _hp = 4;
        _speed = 3;
        _amount = 10;
    }
}

class BasicZombie : GroundEnemy
{
    public BasicZombie(Tpl_BasicZombie template)
        : base(template, TextureManager.Get("zombie_base"), 1, 8)
    { }

    public void StuffHappens()
    { }
}

When loading the game, I would like to go through all the templates in an array to load enemies from them. I know that I could do this manually, but every time I will create a new type of enemy I would need to add it manually to the code (thus probably forgetting more than once).

My two options were: 1- Use a generic, and the above problem ensues. 2- Use a non-generic, and store the type inside, which would anchor the return type Generate() function. This would mean the generate function would output an array of objects, array which would need to be converted to the suitable type every single time a template generates an array of enemies.

I have a space in my head that tells me there is an elegant solution to all this, and I hope it is right!


Solution

  • Jon Skeet's info aside, you might be able to do something like this:

    public MyGeneric<T2> ToOtherType<T2>()
    {
        if (typeof(T2).IsAssignableFrom(typeof(T)))
        {
            // todo: get the object
            return null;
        }
        else
            throw new ArgumentException();
    }
    
            new MyGeneric<Dog>().ToOtherType<Animal>(),
            new MyInheritedGeneric<Cat>().ToOtherType<Animal>()