簡體   English   中英

是否可以將構造函數注入與泛型類型一起使用?

[英]Is it possible to use constructor injection with a generic type?

我有當前具有 class 和方法簽名的事件處理程序,如下所示:

public class MyEventHandler : IIntegrationEventHandler

public void Handle(IntegrationEventintegrationEvent) // IntegrationEvent is the base class

我想做的是這個(以便處理程序可以接受具體類型):

public class MyEventHandler : IIntegrationEventHandler<MyEvent>

public void Handle(MyEvent integrationEvent)

在 Startup.cs 中,我這樣做了:

services.AddTransient<IIntegrationEventHandler<MyEvent>, MyEventHandler>();

問題是注入這些處理程序的服務無法使用打開的 generics,如下所示:

public MyService(IEnumerable<IIntegrationEventHandler<>> eventHandlers)
            : base(eventHandlers)

指定基礎 class 也不起作用:

public MyService(IEnumerable<IIntegrationEventHandler<IntegrationEvent>> eventHandlers)
            : base(eventHandlers)

這給了我 0 個處理程序。

當前方法的工作原理是我注入了所有 7 個處理程序。 但這意味着處理程序必須在其方法中接受基礎 class ,然后對其進行轉換。 沒什么大不了的,但如果我能讓處理程序接受它關心的具體類型,那就太好了。 這可能嗎?

您要求的不能直接完成。 C# collections 始終綁定到特定的項目類型,如果需要不同的類型,則必須轉換該類型。 並且IIntegrationEventHandler<MyEvent>IIntegrationEventHandler<DifferentEvent>是不同的類型。

作為替代解決方案,您可以通過中介(調度程序)調度事件,並使用反射和 DI 將它們路由到具體的處理程序類型。 處理程序將使用它們聲明要處理的特定事件類型向 DI 注冊。 您還可以使用單個處理程序實現來處理多種類型的事件。 調度程序將根據直接從IServiceProvider接收到的事件的運行時類型注入一組具體事件處理程序。

我沒有編譯或測試以下代碼,但它應該給你一個大致的想法。

啟動.cs

services
    .AddTransient<IIntegrationEventHandler<MyEvent>, MyEventHandler>()
    .AddTransient<IntegrationEventDispatcher>();

IntegrationEventDispatcher.cs

private readonly IServiceProvider _serviceProvider;

public IntegrationEventDispatcher(IServiceProvider serviceProvider)
{
    _serviceProvider = serviceProvider;
}

public void Dispatch(IntegrationEvent @event)
{
    var eventType = @event.GetType();

    // TODO: Possibly cache the below reflected types for improved performance
    var handlerType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
    var handlerCollectionType = typeof(IEnumerable<>).MakeGenericType(handlerType);
    var handlers = (IEnumerable)_serviceProvider.GetService(handlerCollectionType);

    var handleMethod = handlerType.GetMethod("Handle", eventType);

    foreach (var handler in handlers)
    {
        handleMethod.Invoke(handler, new[] { @event });
    }
}

我的服務.cs

// ...

private readonly IntegrationEventDispatcher  _eventDispatcher;

public MyService(IntegrationEventDispatcher eventDispatcher)
{
    _eventDispatcher = eventDispatcher;
}

public void DoStuffAndDispatchEvents()
{
    // ...
    _eventDispatcher.Dispatch(new MyEvent());
    _eventDispatcher.Dispatch(new DifferentEvent());
}

編輯:

基於泛型的調度程序實現( gist ):

public void Dispatch<TEvent>(TEvent @event) where TEvent : IntegrationEvent 
{
    var handlers = _serviceProvider.GetRequiredService<IEnumerable<IIntegrationEventHandler<TEvent>>>();

    foreach (var handler in handlers)
    {
        handler.Handle(@event);
    }
}

編輯2:為了支持基本類型的事件處理,我想到了幾種方法:

a) 使用上面的基於反射的方法。

就性能而言,這不是最好的,但它適用於它將收到的任何類型。

b) 使用類型開關

打開事件類型以調用具有正確類型的Dispatch<T> 缺點是您需要列出所有支持的事件類型,並在引入任何新事件類型時更新列表。 此外,這可能有點難以通過測試來捕捉。

IntegrationEventDispatcher邏輯變為

    public void Dispatch(IntegrationEvent @event)
    {
        switch (@event)
        {
            case MyIntegrationEvent e:
                Dispatch(e); // Calls Dispatch<TEvent> as the overload with a more specific type
                break;

            case OtherIntegrationEvent e:
                Dispatch(e);
                break;

            default:
                throw new NotSupportedException($"Event type {@event.GetType().FullName} not supported.");
        }
    }

    private void Dispatch<TEvent>(TEvent @event) where TEvent : IntegrationEvent
    {
        var handlers = _serviceProvider.GetRequiredService<IEnumerable<IEventHandler<TEvent>>>();

        foreach (var handler in handlers)
        {
            handler.Handle(@event);
        }
    }

此處提供了具有最小實現的 Gist。

c) 使用訪客模式

使基本事件類型接受訪問具體事件類型的訪問者。 這是相當多的代碼,需要更改基本事件類型。 當添加新的事件類型時,它會被自動支持,盡管如果使用重載而不是泛型方法(如在適當的訪問者中),可能需要新的重載。

IIntegrationEventVisitor應該與IntegrationEvent存在於同一級別 - 這可能是一個體系結構問題,但是,由於事件已經被設計為對象,我希望有一個行為不應該是一個問題,尤其是這個抽象。

集成事件.cs

public abstract class IntegrationEvent
{
    public abstract void Accept(IIntegrationEventVisitor visitor);
}

MyIntegrationEvent.cs

public class MyIntegrationEvent : IntegrationEvent
{
    public override void Accept(IIntegrationEventVisitor visitor)
    {
        visitor.Visit(this); // "this" is the concrete type so TEvent is inferred properly
    }
}

IIntegrationEventVisitor.cs

public interface IIntegrationEventVisitor
{
    // Note: This is not a proper visitor, feel free to implement 
    // overloads for individual concrete event types for proper design.
    // Generic method is not very useful for a visitor in general
    // so this is actually an abstraction leak.
    void Visit<TEvent>(TEvent @event) where TEvent : IntegrationEvent;
}

IntegrationEventDispatcher.cs

public class IntegrationEventDispatcher : IIntegrationEventVisitor
{
    // Previously known as Dispatch
    public void Visit<TEvent>(TEvent @event) where TEvent : IntegrationEvent
    {
        var handlers = _serviceProvider.GetRequiredService<IEnumerable<IEventHandler<TEvent>>>();

        foreach (var handler in handlers)
        {
            handler.Handle(@event);
        }
    }
}

此處提供了具有最小實現的 Gist。

d) 退后一步

如果你想調度一個基類型,也許注入具體的處理程序根本不是你需要的,可能還有另一種方法。

我們如何創建一個事件處理程序,它接受一個基本類型並僅在它相關時處理它? 但是我們不要在每個處理程序中重復這些相關性檢查,讓我們創建一個 class 來為我們做這件事,並委托給適當的處理程序。

我們將使用IIntegrationEventHandler作為通用處理程序來接受基類型,用通用類型實現它,檢查接受的類型是否相關,然后將其轉發給實現IIntegrationEventHandler<TEvent>的該類型的處理程序。

這種方法可以讓您按照最初的要求在任何地方使用構造函數注入,並且通常感覺最接近您的原始方法。 缺點是所有處理程序都被實例化,即使它們沒有被使用。 這可以通過具體處理程序集合上的Lazy<T>來避免。

請注意,沒有通用的Dispatch方法或其任何變體,您只需注入 IIntegrationEventHandler 的集合,如果接收到的事件類型為IIntegrationEventHandler ,則每個委托給IIntegrationEventHandler<TEvent>TEvent

IIntegrationEventHandler.cs

public interface IIntegrationEventHandler
{
    void Handle(IntegrationEvent @event);
}

public interface IIntegrationEventHandler<TEvent> where TEvent : IntegrationEvent
{
    void Handle(TEvent @event);
}

委托IntegrationEventHandler.cs

public class DelegatingIntegrationEventHandler<TEvent> : IIntegrationEventHandler where TEvent : IntegrationEvent
{
    private readonly IEnumerable<IEventHandler<TEvent>> _handlers;

    public DelegatingIntegrationEventHandler(IEnumerable<IEventHandler<TEvent>> handlers)
    {
        _handlers = handlers;
    }

    public void Handle(IntegrationEvent @event)
    {
        // Check if this event should be handled by this type
        if (!(@event is TEvent concreteEvent))
        {
            return;
        }

        // Forward the event to concrete handlers
        foreach (var handler in _handlers)
        {
            handler.Handle(concreteEvent);
        }
    }
}

MyIntegrationEventHandler.cs

public class MyIntegrationEventHandler : IIntegrationEventHandler<MyIntegrationEvent>
{
    public void Handle(MyIntegrationEvent @event)
    {
        // ...
    }
}

此處提供了具有最小實現的 Gist。 檢查它以了解實際設置和使用情況,因為正確設置它稍微復雜一些。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM