TheHowlingHoaschd
TheHowlingHoaschd

Reputation: 696

C# Covariance puzzle, i encountered

I just encountered an interesting Problem concerning Generics and covariance and, long story short, spent 30 minutes trying to declare a Type until i gave up.

For clarity: I already have a workaround for this, and do not need help with my project currently. I just asked this question because i am a fan of perversely complicated generic types. If you are too, enjoy.

I was trying to define this method to fetch a global IDictionary, that manages all objects of type T by their IDs. (IDs are only unique among objects of the same type).

IDictionary<int, T> getCache<T>() where T : BaseClass { }

To avoid checking T for every derivative of BaseClass (of which there are many) i wanted to define a global Dictionary of Dictionaries, to look up the correct List.

I tried something like this:

Dictionary<Type, IDictionary<int, Baseclass>> allCaches;

Expierienced users of Generics might see the problem with this implementation: the IDictionary<TKey, TValue> interface is not covariant.

(Not covariant means, IDictionary<int, DerivedClass> does NOT inherit from IDictionary<int, BaseClass>. As a result, objects of the former type cannot be placed in my Dictionary allCaches)

I ended up, just using an IDictionary<int, BaseClass> for all my caches, and manually cast back the stored elements when i read them.

I wonder, can anyone think of an implementation for my method getCache<T>() that uses minimal casting and does not manually branch for all types derived from BaseClass?

Upvotes: 3

Views: 134

Answers (2)

Florian Doyon
Florian Doyon

Reputation: 4186

I would use a generic static type as substitute for aDictionary<Type,T> provided I can live with those caches being singletons.

public static class Caches
{
    public static class For<T>
    {
        public static IDictionary<int,T> Cache{get;}=new Dictionary<int,T>(); 
    }

    public static Set<T>(int key,T value)=> For<T>.Cache[k]=v;
}

// and to use the dictionary
public void DoStuff(int i, string value){
    Caches.For<string>.Cache[i]=value;
    // or if you define generic methods in Caches like Set 
    Caches.Set(i,value);
}

Remember that Class<int> and Class<float> are two distinct types who share the same generic type definition Class<T>.

There is no runtime type such as Class<T> as it is an Open Generic Type whereas Class<int> and Class<float> are Closed Generic Types as explained in Tony The Pony's SO answer on Open and Closed Generic Types

This is why static members of Class<float> and Class<int> are distinct, those are two distinct classes that redefine all their members each time the generic parameter changes.

Upvotes: 1

Charles Mager
Charles Mager

Reputation: 26213

I'd be inclined to just ignore the type for the value in your 'dictionary of caches' and cast it on return:

private readonly Dictionary<Type, object> allCaches = new Dictionary<Type, object>();

public IDictionary<int, T> GetCache<T>() where T : BaseClass
{
    object cache;

    if (!allCaches.TryGetValue(typeof(T), out cache))
    {
        cache = new Dictionary<int, T>();
        allCaches.Add(typeof(T), cache);
    }

    return (IDictionary<int, T>) cache;
}

Upvotes: 0

Related Questions