Home > database >  C# Check if type implements a specific generic interface with unknown types
C# Check if type implements a specific generic interface with unknown types

Time:09-01

I have multiple dependency injected handlers and want to find the appropriate one to solve a task. I finally ended up finding the corresponding handler (in class HandlerResolver) but i can't return the Handler as method result.

Interfaces:

public interface IMessage
{
}

public interface IHandler<TRoot, in TMessage> where TRoot : class
{
    public abstract TRoot Apply(TRoot root, TMessage message);
}

// Marker interface to register/resolve dependency injected handlers
public interface IHandler
{
}

Handlers:

public class Handler1: IHandler<MyRoot, Message1>, IHandler
{
    public MyRoot Apply(MyRoot aggregateRoot, Message1 message)
    {
        ...
        return aggregateRoot;
    }
}


public class Handler2: IHandler<MyRoot, Message2>, IHandler
{
    public MyRoot Apply(MyRoot aggregateRoot, Message2 message)
    {
        ...
        return aggregateRoot;
    }
}

Messages:

public class Message1 : ICaseMessage
{
    ...
}

public class Message2 : ICaseMessage
{
    ...
}

DI Registrations:

services.AddScoped<IResolver, HandlerResolver>();
services.AddScoped<IHandler, Handler1>();
services.AddScoped<IHandler, Handler2>();

Resolve Handler:

public class HandlerResolver : IResolver
{
    private IEnumerable<IHandler> Handlers { get; } // DI injected handlers
    public HandlerResolver(IEnumerable<IHandler> handlers)
    {
        Handlers = handlers;
    }


    public IHandler<TRoot, TMessage> GetHandler<TRoot, TMessage>(TRoot root, TMessage message)
        where TRoot : class
        where TMessage : class,
    {
        var concreteRootType = root.GetType();
        var concreteMessageType = message.GetType();

        var handlerOrNull = this.Handlers.FirstOrDefault(p =>
        {
            var genericArguments = p.GetType().GetInterfaces().First().GenericTypeArguments;
            return genericArguments[0] == concreteAggregateType && 
                   genericArguments[1] == concreteMessageType;

        });

        if (handlerOrNull == null)
        {
            throw new NotImplementedException($"No Handler Found");
        }
        else
        {
            return handlerOrNull as IHandler<TRoot, TMessage>;
        }
    }
}

return handlerOrNull as IHandler<TRoot, TMessage>;

This will always return null. I think this is due to the parsing. It seems trying to parse it into a IHandler<TRoot, IMessage> which for some reason doesn't work.

I have also tried this solution How to determine if a type implements a specific generic interface type which doesn't work if the generic type is not known.

CodePudding user response:

Typically you should register the actual full type of your objects, i.e.

services.AddScoped<IHandler<MyRoot, Message1>, Handler1>();

that should let you just get the correct service for your types:

services.GetRequiredService<IHandler<MyRoot, Message1>>()

At least I think that is the correct methods to use for Microsoft.Extensions.DependencyInjection, it is not a library I'm familiar with.

Note that you need to register and resolve the actual types. Resolving IHandler<MyRoot, IMessage> will not work, since that would imply that the handlers could take any kind of IMessage-parameter, but they cannot, they can only take Message1/Message2.

Contravariance works in the opposite direction you seem to think, i.e. you would have a Handler1: IHandler<MyRoot, IMessage> that could be converted to a IHandler<MyRoot, Message1>. That works since the handler promises to accept all IMessage implementation, and Message1 is such an implementation. But it might cause trouble with resolving, since resolving typically require exact type matches.

You might get around that by resolving by hand, i.e. get the type object for your interface from the generic type arguments, and traverse the types by hand, checking if they are assignable. But this is not something I would recommend if you are not trying to do anything weird.

    var handlerType = typeof(IHandler<TRoot, TMessage>);
    Handlers.FirstOrDefault(p => handlerType.IsAssignableFrom(p.GetType()));

The actual handler type should be IHandler<MyRoot, Message1> or IHandler<MyRoot, Message2> depending on the generic type parameters.

  •  Tags:  
  • c#
  • Related