views:

105

answers:

1

Given a particular interface ITarget<T> and a particular type myType, here's how you would determine T if myType implements ITarget<T>. (This code snippet is taken from the answer to an earlier question.)

foreach (var i in myType.GetInterfaces ())
    if (i.IsGenericType
        && i.GetGenericTypeDefinition() == typeof(ITarget<>))
        return i.GetGenericArguments ()[0] ;

However, this only checks a single type, myType. How would I create a dictionary of all such type parameters, where the key is T and the value is myType? I think it would look something like this:

var searchTarget = typeof(ITarget<>);
var dict = Assembly.GetExecutingAssembly().[???]
             .Where(t => t.IsGenericType
                    && t.GetGenericTypeDefinition() == searchTarget)
             .[???];

What goes in the blanks?

+5  A: 
var searchTarget = typeof(ITarget<>);

var dict = Assembly.GetExecutingAssembly()
    .GetTypes()
    .SelectMany(t => t.GetInterfaces()
                      .Where(i => i.IsGenericType
                          && (i.GetGenericTypeDefinition() == searchTarget)
                          && !i.ContainsGenericParameters),
                (t, i) => new { Key = i.GetGenericArguments()[0], Value = t })
    .ToDictionary(x => x.Key, x => x.Value);

Note that if you have multiple classes implementing ITarget<> and using the same generic type argument -- for example, class Foo : ITarget<string> and class Bar : ITarget<string> -- then the ToDictionary call will fail with an ArgumentException complaining that you can't add the same key twice.

If you do need a one-to-many mapping then you have a couple of options available.

  1. Use ToLookup rather than ToDictionary to generate a Lookup<K,V>:

    var dict = Assembly.GetExecutingAssembly()
        .GetTypes()
        .SelectMany(/* ... */)
        .ToLookup(x => x.Key, x => x.Value);
    
  2. If you prefer to work with something like a Dictionary<K,List<V>> then you could do this:

    var dict = Assembly.GetExecutingAssembly()
        .GetTypes()
        .SelectMany(/* ... */)
        .GroupBy(x => x.Key, x => x.Value)
        .ToDictionary(g => g.Key, g => g.ToList());
    
LukeH
Alternate: `AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes(). //etc`
Will
Worked perfectly! Thanks very much. One question, though: what is the motivation for adding `!i.ContainsGenericParameters`?
James Wenday
`!i.ContainsGenericParameters` excludes `class C<T> : ITarget<T> { ... }`.
Anton Tykhyy
@Luke: I think the limitation you noted is fine, as I only have one implementer of `T` for any suitable `T` of `ITarget<T>`. Just out of curiosity, though, is it possible to have the `ToDictionary` call create a _list_ of suitable types as its value instead of only a single type?
James Wenday
@James: You could call `ToLookup` to generate a `Lookup<K,V>` instead, or you could use `GroupBy` and then generate a `Dictionary<K,List<V>>` or something similar. I've updated my answer with a few details.
LukeH
This is a great answer. Thanks Luke! Also, Will: in my particular case I do actually want only the types in my assembly, not all loaded assemblies, but your way is good to know about too.
James Wenday