Search code examples
c#dependency-injectionautofaccontravariancemarker-interfaces

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


Solution

  • 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.