2

I would like to know if there is way to store in a Dictionary/List/... of a generic type.

Let's imagine this class:

public class Registry{
    private Dictionary<String, MyGenericType<IContainableObject>> m_elementDictionary = new Dictionary<String, MyGenericType<IContainableObject>>();

    public void Register<T>(MyGenericType<T> objectToRegister)
    where T: IContainableObject
    {
        m_elementDictionary[objectToRegister.key] = objectToRegister; //This doesn't work
    }
}

I don't understand why we can't add this element to the Dictionary since we know that the argument we receive with a generic type is in fact a MyGenericType<IContainableObject> due to the where condition.

Please note:

  1. I know that I can put an interface on MyGenericType<IContainableObject> a store a dictionary of this. This is the subject.
  2. I know that I can have an MyGenericType<IContainableObject>argument, this is the point either.

I was more looking if the covariance/contravariance can help here?

J4N
  • 19,480
  • 39
  • 187
  • 340
  • 1
    It's a matter of variance/covariance, take a look here: https://msdn.microsoft.com/en-us/library/ee207183.aspx – Salvatore Sorbello Jan 21 '15 at 08:01
  • I already tried to understand this page, but I admit I didn't totally get it. In my case, is there something I could specify to make my usecase possible? – J4N Jan 21 '15 at 08:20
  • I'm trying to make a working example for you – Salvatore Sorbello Jan 21 '15 at 08:21
  • The problem is that `MyGenericType` is not a superclass of `MyGenericType`. Generic types are also types (not just the type they are generic for), so there's no implicit conversion between them. `MyGenericType` would need to be an interface to allow variance. – Jcl Jan 21 '15 at 08:39

3 Answers3

1

You should express the where condition like this:

public void Register<T>(T objectToRegister)
    where T : MyGenericType<IContainableObject> {
    m_elementDictionary[objectToRegister.key] = objectToRegister;
}

Moreover, you should define MyGenericType to be covariant, as in this example:

interface IContainableObject { 
}

public interface MyGenericType<out T> {
    string key();
}

interface IDerivedContainableObject : IContainableObject {
}

class Program {

    private static Dictionary<String, MyGenericType<IContainableObject>> m_elementDictionary = new Dictionary<String, MyGenericType<IContainableObject>>();

    public static void Register<T>(T objectToRegister)
        where T : MyGenericType<IContainableObject> {
            m_elementDictionary[objectToRegister.key()] = objectToRegister;
    }

    static void Main(string[] args) {
        MyGenericType<IDerivedContainableObject> x = null;
        MyGenericType<IContainableObject> y = x;
        Register(y);
    }

}

(Note that MyGenericType is now an interface)

Paolo Tedesco
  • 55,237
  • 33
  • 144
  • 193
  • In that case you could also simply write `public void Register(MyGenericType objectToRegister)` – Oliver Jan 21 '15 at 08:18
  • Well, it only moves the issue, with this signature, you will not be able to call the `Register` method with an `MyGenericType – J4N Jan 21 '15 at 08:18
  • @J4N: right, I've tried to set up a working example now, but it requires some changes in your code... – Paolo Tedesco Jan 21 '15 at 08:31
  • The covariant definition is the `out`? – J4N Jan 21 '15 at 09:04
  • I tested and it seems to work, but is there any reason that we cannot put this covariance `out` attribute on a class but only an interface? – J4N Jan 21 '15 at 09:16
  • Check these answers: http://stackoverflow.com/questions/2541467/why-does-c-sharp-4-0-not-allow-co-and-contravariance-in-generic-class-types and http://stackoverflow.com/questions/2733346/why-isnt-there-generic-variance-for-classes-in-c-sharp-4-0/2734070#2734070 – Paolo Tedesco Jan 21 '15 at 13:27
0

The reason this won't work is because the C# standard defines generics to be invariant [1]. This means that if we have a base class (or interface) B and a derived class D then we can say the followings:

  • D is a subtype of B
  • D[] is a subtype of B[]
  • BUT G<D> is not a subtype of G<B> where G is any generic type.

The compiler will try to do an implicit conversion between two invariant types G<D> and G<B> and surely will fail since no conversion is defined there.

And this happens to be your case as well since you are trying to convert from MyGenericType<some_object> to MyGenericType<IContainableObject>.

Note that semantically speaking this actually makes sense since you are not really converting from a derived class to a base class but more between two generic types.

Same behavior can be noticed with lists:

List<D> base_list = new List<D>(); //this will give an error message

I am unware of your specific requirements to give relevant suggestions, but in most cases I would most likely go with hiding this implemention under an interface and storing those interfaces in the dictionary (what you already mentioned it). This will provide a free decoupling as well.


References

[1] Generic type parameter variance in the CLR

Memleak
  • 158
  • 7
  • Yes, it's what I taught, but isn't there a way to specify that in a specific case, we allow one generic parameter to be covariant? – J4N Jan 21 '15 at 08:27
  • As Paolo Tedesco already mentioned in his answer (so I won't duplicate it in mine) you could define an interface that has a covariant template parameter but how much of a fit it is in your problem really depends on the constraints you have. Aim for the simplest solution that works in your design. If this is some generic behavior you are trying to implement than covariant generics might be worth it, otherwise you can attempt a simpler interface based design. – Memleak Jan 21 '15 at 08:37
  • Feel free to create a a post on [codereview.se] once you get to a solution for extra suggestions, if you wish that. – Memleak Jan 21 '15 at 08:38
0

I'm not sure this will be clear, but its working:

public interface IContainableObject 
{ 
}

public interface IMyGenericType<in T> 
    where T:IContainableObject
{
    string key{get;set;}
}

public abstract class MyGenericType<T> : IMyGenericType<IContainableObject>
    where T : IContainableObject
{
    public string key{get;set;}
}

public class MyTypedClass:MyGenericType<DerivedContainableObject>
{

}

public class DerivedContainableObject:IContainableObject
{
}



public class Registry

{
    private Dictionary<String, IMyGenericType<IContainableObject>> m_elementDictionary = new Dictionary<String, IMyGenericType<IContainableObject>>();

    public void Register<T>(MyGenericType<T> objectToRegister)       
        where T:IContainableObject
    {
        m_elementDictionary[objectToRegister.key] = objectToRegister; //This now work
    }

    public void ExampleMethod() 
    {
        Register<DerivedContainableObject>(new MyTypedClass());
    }
}
  • What is the difference between the `in` you specify and the `out`that Paolo indicate in his answer? – J4N Jan 21 '15 at 11:41
  • The `out` keyword marks a type parameter as covariant, and the `in` keyword marks it as contravariant. The two most important rules to remember: You can mark a generic type parameter as covariant if it is used only as a method return type and is not used as a type of formal method parameters. And vice versa, you can mark a type as contravariant if it is used only as a type of formal method parameters and not used as a method return type. So if in your `MyGenericType` you use `T` as an input of your methods you will put `in`, otherwise you will put `out` – Salvatore Sorbello Jan 21 '15 at 13:06
  • You should write MSDN documentation, much easier to understand here. In the question I showed that I used the generic type as an input, but in my whole real case, I have the same register, but also a method that return a registred object(so, the generic type). Currently the compiler doesn't throw any error, why? Since it's used as input and output? – J4N Jan 21 '15 at 16:36
  • I've one more small issue with this: in fact in my `IMyGenericType` I've to provide a getter and a setter for the contained value. The issue is that I've basically to give a `Func` and a `Action`, but I cannot have the same "variance" between my two generic args, one time it's IN, the other time it's out. I can basically do what I want with putting two generic types, but I've to propagate this changes in the calling methods. Is there a way to avoid this? – J4N Jan 23 '15 at 15:53