3

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?

Community
  • 1
  • 1

1 Answers1

6
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
  • 263,068
  • 57
  • 365
  • 409
  • 2
    Alternate: `AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes(). //etc` –  May 24 '10 at 12:27
  • Worked perfectly! Thanks very much. One question, though: what is the motivation for adding `!i.ContainsGenericParameters`? – James Wenday May 24 '10 at 12:28
  • `!i.ContainsGenericParameters` excludes `class C : ITarget { ... }`. – Anton Tykhyy May 24 '10 at 12:33
  • @Luke: I think the limitation you noted is fine, as I only have one implementer of `T` for any suitable `T` of `ITarget`. 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 May 24 '10 at 12:51
  • @James: You could call `ToLookup` to generate a `Lookup` instead, or you could use `GroupBy` and then generate a `Dictionary>` or something similar. I've updated my answer with a few details. – LukeH May 24 '10 at 13:17
  • 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 May 24 '10 at 15:14