views:

200

answers:

2

I often find myself in a situation where I create a generic interface or class and then want to use different versions of this class or interface in a non generic way. For example I may have an interface like this:

interface ICanCreate<T>
{
    T NewObject();
}

Which allows a class to be a factory for that type. I then want to register these with a general factory class, so I try to write something like this:

public class Factory
{
    private Dictionary<Type, ICanCreate> mappings; // what do I put here????

    public void RegisterCreator<T>(ICanCreate<T> creator)
    {            
    }

    public T Create<T>()
    {            
    }
}

In the dictionary which type do I use for my value? I don't know if I am missing some sort of design principle and I am aware that this has a lot to do with co(ntra?)variance. Any help or ideas would be much appreciated.

+6  A: 

You either have to just use object in your dictionary declaration (it's all private, and you can verify that you'll never put the wrong kind of thing in there) or declare a non-generic ICanCreate interface which ICanCreate<T> extends.

Basically you want a type relationship which can't be expressed in C# - and whenever that happens, you end up with a slightly unpleasant solution, but it looks like you can isolate the ugliness here (i.e. keep it within the single class).

Jon Skeet
What would you say is the most preferred one of the two? I always go for solution number two if I am in this situation, believing that it is a more elegant solution. Is it, or does it not matter?
Razzie
Are there any members you could sensibly put in the non-generic interface? If the interface is *just* the Create method, then I'd stick with object - the non-generic interface wouldn't actually give you anything. In other cases, you may be able to get away with only referring to the non-generic interface where you don't know the right type argument, in which case that provides an obvious benefit.
Jon Skeet
Clear, thanks for the comment!
Razzie
+2  A: 

Interestingly, this is a problem that is solved in C# 4.0:

public interface ICanCreate<out T> // covariant
{
    T NewObject();
}

public class Factory
{
    private Dictionary<Type, ICanCreate<object>> mappings = new Dictionary<Type, ICanCreate<object>>();

    public void RegisterCreator<T>(ICanCreate<T> creator) where T:class
    {            
      mappings[typeof(T)] = creator;
    }

    public T Create<T>()
    {            
      ICanCreate<object> creator = mappings[typeof(T)];
      return (T) creator.NewObject(); // I do not think you can get rid of this cast
    }
}
Rasmus Faber
What does `out T` do?
Svish
@Svish: It marks T as covariant.
Rasmus Faber
Hm.. what does that mean? Do you have a link with more info on that or something?
Svish
Eric Lippert's series on covariance and contravariance is one of the best explanations on the net. It starts here: http://blogs.msdn.com/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx
Rasmus Faber
Thanks! Will have a look at that =) Is the `out` marking thing mentioned there as well, or is that something new in C# 4.0? If new, do you have something about that somewhere?
Svish
Those articles was written before the new syntax was finalized, so Eric uses + and - instead of out and in. Most articles about the new features in C# 4.0 will mention the new support for co/contravariance. Here is one: http://community.bartdesmet.net/blogs/bart/archive/2009/04/13/c-4-0-feature-focus-part-4-generic-co-and-contra-variance-for-delegate-and-interface-types.aspx
Rasmus Faber