Search code examples
c#dryioc

Resolve all registered implementations of an open generic interface in DryIoc


Suppose we have an interface:

public interface ICanFetch<TItem, TParams>
{
    Task<List<TItem>> FetchItemsAsync(TParams @params);
}

I implement this interface in different DAOs. Now I have a use case where I need to look up all the implementations of this interface from the container (through DI or directly from the container), think implementations of ICanFetch<Item1, Params1>, ICanFetch<Item2, Params2> etc.

I tried to do something like this:

var items = container.ResolveMany(typeof(ICanFetch<,>));

But apparently the container doesn't work like this, because despite me registering everything using [RegisterMany] (using MEF attribute model) which works like intended on other use cases, I can't resolve all the fetchers. Is there something I am doing wrong, or is it simply not implemented? If the latter - can I somehow implement that myself? Apart from introducing covariance to the interface because Tasks and covariance are not compatible.


Solution

  • The main issue is that ICanFetch<,> is an open generic type and ICanFetch<Item1, Params1> is a closed generic type (generic type with provided generic type parameters, Item1 and Params1).

    There is no inheritance relationship between an open and a closed type, i.e. ICanFetch<Item1, Params1> is not ICanFetch<,>.

    For example with familiar Dictionary:

    (typeof(Dictionary<,>)).IsAssignableFrom(typeof(Dictionary<int, int>)); // False
    
    
    (typeof(Dictionary<,>)).IsAssignableTo(typeof(Dictionary<int, int>)); // False
    

    Fortunately, the built-in reflection API of GetGenericTypeDefinition allows you to get the open generic type that a closed one is based of:

    (typeof(Dictionary<int, int>).GetGenericTypeDefinition() == typeof(Dictionary<,>)); // True
    

    This is exactly what we want in our case - to be able to match ICanFetch<Item1, Params1> with ICanFetch<,>.

    However, this won't work with ResolveMany since its API just allows for type comparisons. But it will with GetServiceRegistrations for us to get the service type that we can subsequently use in a SelectMany with ResolveMany to get our instances.

    var items = container.GetServiceRegistrations()
        .Where(x =>
            x.ServiceType.IsGenericType && 
            x.ServiceType.GetGenericTypeDefinition() == (typeof(ICanFetch<,>)))
        .SelectMany(x => container.ResolveMany(x.ServiceType, serviceKey: x.OptionalServiceKey))
        .ToList();
    

    Caveat, is that I haven't used DryIoc. Please comment if something is to correct in the Linq to achieve the same functionality as originally intended.