Autofac 和逆变:解析为更多派生类型

Posted

技术标签:

【中文标题】Autofac 和逆变:解析为更多派生类型【英文标题】:Autofac and contravariance : resolving to more derived type 【发布时间】:2017-03-19 18:27:25 【问题描述】:

正在编写一个通用消息处理程序,需要通过 AutoFac 获取各种消息处理程序。消息处理程序的基本定义是:

public interface IMessageHandler<in TMessage> :
    IMessageHandler
    where TMessage : IMessage

    Task<IMessageResult> Handle(TMessage message);

我还定义了一个标记接口,以便可以在 AutoFac 中轻松注册这些接口

public interface IMessageHandler


一个示例消息处理程序是:

public class CreatedEventHandler : IMessageHandler<CreatedEvent>

    public Task<IMessageResult> Handle(CreatedEvent message)
    
        // ...
    

这些都是通过 Autofac 很好地注册的,使用

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

这一切都很好。但是,当我需要解决处理程序时,我遇到了问题

// 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>;

为什么上面的强制转换返回 null?即使在IMessageHandler&lt;&gt; 接口中定义了逆变。

如何解决合适的处理程序?

谢谢

【问题讨论】:

这与 Autofac 无关。这就是变异在 C#/CLR 中的工作原理。 【参考方案1】:

哎呀!

// Covariance
handler as IMessageHandler<IMessage>;

您的handler 有一个通用参数,它不是IMessage,而是IMessage 实现。因此,这是协方差(你正在向上转换一个通用参数)。

由于我不了解您的实际软件架构,因此无法为您提供解决方案。至少,您知道为什么整个演员阵容都会导致null

不费吹灰之力的可能解决方案...

您的消息处理程序既可以实现IMessageHandler&lt;ConcreteEvent&gt;,也可以实现一个新的非泛型接口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);
    

现在整个演员表都可以工作了,因为您的类将显式实现IMessageHandler&lt;IMessage&gt;

为了避免重复自己太多,你可以实现一个抽象类:

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);
        

最后,您的具体消息处理程序如下所示:

public class CreatedEventHandler : MessageHandler<CreatedEvent>

    public Task<IMessageResult> Handle(CreatedEvent message)
    
        // ...
    

也就是说,您的演员可以只转换为handler as IMessageHandler

【讨论】:

这看起来是一个优雅的解决方案。但是,我在 MessageHandler 抽象类上遇到编译错误:'MessageHandler' 无法同时实现 'IMessageHandler' 和 'IMessageHandler' 因为它们可能会针对某些类型参数替换进行统一 @sppc42 嗯,你是对的。现在我已经放弃了那部分。我稍后会修改我的答案;) @sppc42 检查我的编辑答案。现在我引入了一个非通用接口来满足我们之前的答案文本无法实现的相同要求。 @sppc42 没问题,不客气!我很高兴它在你的场景中运行良好。我现在担心它是否只是设计缺陷上的 patch 或者它是您场景中的正确解决方案。这个考虑现在由你决定! ;)

以上是关于Autofac 和逆变:解析为更多派生类型的主要内容,如果未能解决你的问题,请参考以下文章

协变和逆变

进入快速通道的委托(深入理解c#)

C#中的协变与逆变

C#中的协变与逆变

C# - 协变逆变 看完这篇就懂了

协变和逆变之疑问