views:

91

answers:

4

I have a number of classes all in the same interface, all in the same assembly, all conforming to the same generic interface:

public class AppleFactory : IFactory<Apple> { ... }
public class BananaFactory : IFactory<Banana> { ... }
// ...

It's safe to assume that if we have an IFactory<T> for a particular T that it's the only one of that kind. (That is, there aren't two things that implement IFactory<Apple>.)

I'd like to use reflection to get all these types, and then store them all in an IDictionary, where the key is typeof(T) and the value is the corresponding IFactory<T>. I imagine eventually we would wind up with something like this:

_map = new Dictionary<Type, object>();

foreach(Type t in [...]) {
  object factoryForType = System.Reflection.[???](t);
  _map[t] = factoryForType;
}

What's the best way to do that? I'm having trouble seeing how I'd do that with the System.Reflection interfaces.

A: 

The problem will come in matching all IFactory because you have to specify T. The simplest solution will be to create another interface IFactory and make IFactory<T> be the generic version of it. Then you can match all IFactory and then build the dictionary.

Assembly.GetExecutingAssembly().GetTypes()
    .Where(t => t is IFactory)
    .ToDictionary(k => k.GetGenericArguments()[0],v => v);
Stephan
Hmm -- but how come I can do `Type blankFactory = typeof(IFactory<>)`? Clearly the compiler can deal just fine with missing type parameters -- seems like this should be doable in reflection, which is the part that's stumping me.
Kevin Brassen
+1  A: 

In order to identify classes which are generic definitions of an interface I use the following extension method:

public static class TypeExtensions
{
    public static bool IsGenericTypeOf(this Type t, Type genericDefinition)
    {
        return t.IsGenericType && genericDefinition.IsGenericType && t.GetGenericTypeDefinition() == genericDefinition.GetGenericTypeDefinition();
    }
}

Then you can use the Type.GetGenericArguments method find "T".

Such that you'd end up with something like:

var allTypes = ...; // Maybe: Assembly.GetExecutingAssembly().GetTypes()
var dictionary = allTypes.Where(t => t.IsGenericTypeOf(typeof(IFactory<>))).ToDictionary(t => t.GetGenericArguments()[0], t => t);
Reddog
+1  A: 
        foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
        {
            bool isIFactory = (from i in type.GetInterfaces()
                               where i.IsGenericType &&
                                     i.GetGenericTypeDefinition() == typeof(IFactory<>) &&
                                     i.IsAssignableFrom(i.GetGenericArguments()[0])
                                     // this one could be also changed to i.GetGenericArguments()[0] == type
                                     // however, it'll generate an anonymous class, which will show in the outer foreach
                               select i).Count() == 1;
        }
Franci Penov
+1  A: 
private readonly Dictionary<Type, object> _map = Assembly.GetExecutingAssembly()
    .GetTypes()
    .SelectMany(t => t.GetInterfaces()
                      .Where(i => i.IsGenericType
                          && (i.GetGenericTypeDefinition() == typeof(IFactory<>))
                          && !i.ContainsGenericParameters),
                (t, i) => new
                    {
                        KeyType = i.GetGenericArguments()[0],
                        Factory = Activator.CreateInstance(t)
                    })
    .ToDictionary(x => x.KeyType, x => x.Factory);

// ...

public IFactory<T> GetFactory<T>()
{
    object factory;
    if (_map.TryGetValue(typeof(T), out factory))
        return (IFactory<T>)factory;

    // no factory found so return null (or throw an exception if you prefer)
    return null;
}
LukeH