[英]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.