中介模式和简单注入器的逆变

Posted

技术标签:

【中文标题】中介模式和简单注入器的逆变【英文标题】:Mediator pattern and contravariance with Simple Injector 【发布时间】:2015-03-10 16:25:24 【问题描述】:

这个问题源于我正在尝试为 MediatR 创建一个简单的注入器实现:https://github.com/jbogard/MediatR/pull/14。

我在尝试解决通用处理程序接口的实现时遇到了麻烦。考虑以下通知处理程序接口:

public interface INotificationHandler<in TNotification>
    where TNotification : INotification

    void Handle(TNotification notification);

INotifcation 只是一个空的标记界面。

我为Pinged(实现INotification)事件定义了以下处理程序:

public class PingedHandler : INotificationHandler<Pinged>

    public void Handle(Pinged notification)  


public class PingedHandler2 : INotificationHandler<Pinged>

    public void Handle(Pinged notification)  

还有一个通用处理程序(注意这应该处理每个INotification):

public class GenericHandler : INotificationHandler<INotification>

    public void Handle(INotification notification)  

通过以下注册:

var container = new Container();

container.RegisterManyForOpenGeneric(
    typeof (INotificationHandler<>),
    (service, impls) => container.RegisterAll(service, impls),
    AppDomain.CurrentDomain.GetAssemblies());

现在我期待:

GetAllInstances<INotificationHandler<Pinged>>();

解决它所做的PingedHandlerPingedHandler2。但它不能解析GenericHandler,因为它实现了INotificationHandler&lt;INotification&gt; 而不是INotificationHandler&lt;Pinged&gt;。我想知道是否有办法让 Simple Injector 搜索整个对象图并解析 anything,也就是 Pinged

我从 Steven 那里找到了 a blog post 关于协变和逆变的信息,但我无法让它适用于我的示例。

【问题讨论】:

INotificationHandler&lt;INotification&gt;INotificationHandler&lt;Pinged&gt; 不兼容,所以这并不奇怪。如果您想要所有处理程序,您是否尝试过GetAllInstances&lt;INotificationHandler&lt;INotification&gt;&gt; @Lee container.GetAllInstances&lt;INotificationHandler&lt;INotification&gt;&gt;() 只解析 GenericHandler,这是我期望它做的。我想要的是GenericHandler 总是被解决,因为每个通知都应该实现INotification @Lee 但INotificationHandler&lt;Pinged&gt; INotificationHandler&lt;INotification&gt; 兼容。 @Lee [Fact] public void GenericHandler_CanBe_CalledWithAnInstanceOfPinged() var handler = new GenericHandler(); Assert.DoesNotThrow(() =&gt; handler.Handle(new Pinged())); 您遇到的是 v2.6 中的限制/错误。如果您升级到 v2.7.0-beta1,您将看到您可以使用@qujck 在他的回答中显示的RegisterAll 注册来解决非通用GenericHandler 【参考方案1】:

更新

从 Simple Injector 版本 2.7 开始,this functionality 现在是标准的,您不再需要下面显示的解决方法。


您缺少该文章中描述的MultipleDispatchEventHandler 的变体。采用基本逻辑并将其应用于您的抽象确实会产生您期望的结果:

[Fact]
public void RegisterAll_Contravariant_Succeeds()

    var container = new Container();

    container.RegisterManyForOpenGeneric(
        typeof(INotificationHandler<>),
        (service, impls) => container.RegisterAll(service, impls),
        AppDomain.CurrentDomain.GetAssemblies());

    var handlersType = typeof(IEnumerable<INotificationHandler<Pinged>>);

    var handlersCollection = (
        from r in container.GetCurrentRegistrations()
        where handlersType.IsAssignableFrom(r.ServiceType)
        select r.GetInstance())
        .Cast<IEnumerable<INotificationHandler<Pinged>>>()
        .ToArray();

    var result = 
        from handlers in handlersCollection
        from handler in handlers
        select handler;

    Assert.Equal(3, result.Count());

但将GenericHandler 设为通用可能更容易:

public class GenericHandler<TNotification> : INotificationHandler<TNotification>
    where TNotification : INotification

    public void Handle(TNotification notification)  

提供更简单的注册

[Fact]
public void RegisterAll_WithOpenAndClosedGenerics_Succeeds()

    var container = new Container();

    var types = OpenGenericBatchRegistrationExtensions
        .GetTypesToRegister(
            container,
            typeof(INotificationHandler<>),
            AccessibilityOption.AllTypes,
            AppDomain.CurrentDomain.GetAssemblies())
        .ToList();

    types.Add(typeof(GenericHandler<>));

    container.RegisterAll(typeof(INotificationHandler<>), types);

    var result = container.GetAllInstances<INotificationHandler<Pinged>>().ToList();

    Assert.Equal(3, result.Count());

【讨论】:

【参考方案2】:

tl;dr:这是 Simple Injector v2.6.0 中的错误/缺点,已在 v2.7.0 中修复。问题中的配置(如下所示)现在可以正常工作。


总结@qujck 的回答和@Steven 的评论:我能够通过安装具有以下配置的 Simple Injector v2.7.0-beta2 来使其工作(实际上与问题中的相同):

// Simple Injector v3.x
container.RegisterCollection(typeof(INotificationHandler<>),
    AppDomain.CurrentDomain.GetAssemblies());

// Simple Injector v2.x
container.RegisterManyForOpenGeneric(
    typeof(INotificationHandler<>),
    container.RegisterAll,
    AppDomain.CurrentDomain.GetAssemblies());

现在 Simple Injector 能够在请求时解析PingedHandlerPingedHandler2 GenericHandler

container.GetAllInstances<INotificationHandler<Pinged>>();

【讨论】:

我更新了您对 v2.7.0-beta2 的回答。 2.7.0-beta1 中的一个错误导致注册变得不必要的复杂。注册现在是您所期望的。 感谢@Steven!你打算什么时候发布这个? Henk,Simple Injector 2.7 已经发布。 IMO 这个答案应该被标记为'the' answer。

以上是关于中介模式和简单注入器的逆变的主要内容,如果未能解决你的问题,请参考以下文章

中介者模式

关于中介者模式

重学设计模式(三设计模式-中介者模式)

重学设计模式(三设计模式-中介者模式)

设计模式这样玩泰简单(Golang版)-中介模式

设计模式这样玩泰简单(Golang版)-中介模式