简体   繁体   中英

Autofac and contravariance : resolving to more derived type

Am writing a generic message handler and need to get various message handlers via AutoFac. The basic definition of the message handler is:

public interface IMessageHandler<in TMessage> :
    IMessageHandler
    where TMessage : IMessage
{
    Task<IMessageResult> Handle(TMessage message);
}

I have also defined a marker interface so that these can be easily registered in AutoFac

public interface IMessageHandler
{
}

A Sample message handler is:

public class CreatedEventHandler : IMessageHandler<CreatedEvent>
{
    public Task<IMessageResult> Handle(CreatedEvent message)
    {
        // ...
    }
}

And these are nicely registered named via Autofac using

builder.RegisterAssemblyTypes(assemblies)
       .Where(t => typeof(IMessageHandler).IsAssignableFrom(t))
       .Named<IMessageHandler>(t => t.Name.Replace("Handler", string.Empty))
       .InstancePerLifetimeScope();

This all works fine. However, when I need to resolve a handler, i have an issue

// handler returned is non null and of type marker interface IMessageHandler
var handler = container.Resolve("CreatedEvent");

// This is null. I just can't understand why
var createdEventHander = handler as IMessageHandler<IMessage>;

Why is the cast above returns null? Even though the contravariance is defined in the IMessageHandler<> interface.

How can I resolve the appropriate handlers?

Thanks

Oops!

// Covariance
handler as IMessageHandler<IMessage>;

Your handler has a generic argument that isn't IMessage but an IMessage implementation. Thus, this is covariance (you're upcasting a generic argument).

Since I don't know your actual software architecture I can't provide you a solution. At least, you know why the whole cast results in null .

Possible solution with a very little effort...

Your message handlers could both implement IMessageHandler<ConcreteEvent> and a new non-generic interface IMessageHandler :

public interface IMessageHandler
{
      Task<IMessageResult> Handle(IMessage message);
}

public interface IMessageHandler<TMessage> : IMessageHandler
       where TMessage : IMessage
{
       Task<IMessageResult> Handle(TMessage message);
}

public class CreatedEventHandler : IMessageHandler<CreatedEvent>
{
    public Task<IMessageResult> Handle(CreatedEvent message)
    {
        // ...
    }

    // I would implement the non-generic Handle(IMessage) explicitly
    // to hide it from the public surface. You'll access it when successfully
    // casting a reference to IMessageHandler
    Task<IMessageResult> IMessageHandler.Handle(IMessage message) 
    {
         return Handle((CreatedEvent)message);
    }
}

Now the whole cast will work because your classes will explicitly implement IMessageHandler<IMessage> .

And to avoid repeating yourself too much, you can implement an abstract class:

public abstract class MessageHandler<TMessage> : IMessageHandler<TMessage>
       where TMessage : IMessage
{
        public abstract Task<IMessageResult> Handle(TMessage message);

        // I would implement the non-generic Handle(IMessage) explicitly
        // to hide it from the public surface. You'll access it when successfully
        // casting a reference to IMessageHandler
        Task<IMessageResult> IMessageHandler.Handle(IMessage message) 
        {
             return Handle((TMessage)message);
        }
}

Finally, your concrete message handlers would look as follows:

public class CreatedEventHandler : MessageHandler<CreatedEvent>
{
    public Task<IMessageResult> Handle(CreatedEvent message)
    {
        // ...
    }
}

That is, your cast can be turned to just handler as IMessageHandler .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM