Question

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!

Was it helpful?

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>()

OTHER TIPS

Yes, C# 4 supports generic variants - but only in the declarations of interfaces and delegates, so you won't be able to do it in this case. Of course you could potentially create an interface:

public interface IGeneric<out T>

and then implement that in your classes, at which point you could create an IGeneric<Animal>.

If you can give more details about what you're trying to achieve, we may be able to help you find an alternative approach.

If an array is going to hold more than one type of item, the items are going to have to be stored in heap objects which are separate from the array itself (if some of the types are structs, they'll have to either be boxed or stored as a field in a generic type which derives from a non-generic one). In most cases, the simplest thing to do will be to identify a common ancestor type for everything you'll be storing in the array, and simply typecast array elements as needed. There are a few cases where that won't be feasible, however. If, for example, your collection is going to hold objects whose type is unknown but is constrained to more than one interface, it will be necessary to pass those objects to generic routines whose method type parameter is similarly constrained, and the types that may be passed to your routine have no common ancestor which satisfies all constraints, there won't be any single type to which all members of your collection can be cast that would allow them to be passed as a suitable generic.

If the objects in your collection will only be passed to a small number of routines, it may be possible to have the generic method which adds items construct delegates to suitably invoke all the necessary routines and store those delegates as part of the collection. Lambda expressions or anonymous delegates may be convenient for this.

For example, suppose one will need to be able to feed items that are stored in a list to the Wibble<T> method of various IWibbler objects and the Wobble<T> method of various IWobbler objects, where the T types have interface constraints I1 and I2.

    interface IWibbler { void Wibble<T>(T param, int param) where T : I1,I2; }
    interface IWobbler { void Wobble<T>(T param, string param) where T: I1,I2; }

    private struct WibbleWobbleDelegateSet 
    {
        public Action<IWibbler, int> Wibble;
        public Action<IWobbler, string> Wobble;
        static WibbleWobbleDelegateSet Create<T>(T param) where T: I1, I2
        {
            var ret = new WibbleWobbleDelegateSet ();
            ret.Wibble = (IWibbler wibbler, int p2) => { wibbler.Wibble<T>(param, p2); };
            ret.Wobble = (IWobbler wobbler, string p2) => { wobbler.Wobble<T>(param, p2); };
            return ret;
        }
    }

Calling WibbleWobbleDelegateSet.Create<T>(T param), with a suitably-constrained param, will yield a non-generic structure which contains delegates that can be used to pass the parameter supplied at struct creation to any IWibbler.Wibble<T>() or IWobbler.Wobble<T>() method.

This approach is only directly usable if the list of routines that will be called is known. If one needs to be able to call arbitrary routines with constrained generic parameters, it's possible to do that either with some tricky interfaces or with Reflection, but such things get more complicated.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top