sppc42 sppc42 - 28 days ago 13
C# Question

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

Answer

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.

Comments