我正在尝试优化我的代码,以注入一系列实现接口的类。IEventHandler<TEvent>
。
我具有以下结构:
public interface IEventHandlerMarker { }
public interface IEventHandler<in TEvent> : IEventHandlerMarker where TEvent : IEvent
{
Task Handle(TEvent eventItem);
}
public interface IEvent
{
public DateTime Timestamp { get; set; }
}
我在DI中注册了标记接口IEventHandlerMarker
,并且在访问处理程序时,目前正在执行以下操作:
public EventPublisherService(IEnumerable<IEventHandlerMarker> eventHandlers)
{
// Check and and all event handlers
foreach (IEventHandlerMarker item in eventHandlers)
{
AddEventHandler(item);
}
}
在我的AddEventHandler
方法中,我将其过滤为IEventHandler<>
,如下所示:
Type handlerType = eventHandlerMarker.GetType().GetInterfaces()
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEventHandler<>));
到目前为止,一切正常,但是我想摆脱标记接口和过滤器逻辑。因此,我将处理程序的注册更改为以下方法,这似乎按预期工作:
public static IServiceCollection AddEventHandlers(this IServiceCollection serviceDescriptors, params Assembly[] handlerAssemblies)
{
Type genericHandlerType = typeof(IEventHandler<>);
foreach (var implementationType in genericHandlerType.GetTypesWithGenericInterfacesInAssemblies(handlerAssemblies))
{
Type interfaceType = implementationType.GetGenericInterfaceType(genericHandlerType);
serviceDescriptors.AddSingleton(interfaceType, implementationType);
}
return serviceDescriptors;
}
public static List<Type> GetTypesWithGenericInterfacesInAssemblies(this Type source, params Assembly[] assemblies)
{
return assemblies
.SelectMany(currentAssembly => currentAssembly.GetTypes()
.Where(type => type.GetInterfaces().Any(
interfaceItem => interfaceItem.IsGenericType
&& interfaceItem.GetGenericTypeDefinition().Equals(source))))
.ToList();
}
我将EventPublisherService的构造函数更改为以下内容:
public EventPublisherService(IServiceProvider serviceProvider)
{
Type ienumerableOfIEventHandlerType = typeof(IEnumerable<>).MakeGenericType(typeof(IEventHandler<>));
object result = serviceProvider.GetService(ienumerableOfIEventHandlerType);
}
但是结果总是显示为空。我在Google上搜索并检查了一些关于Stackoverflow的文章,并发现了以下文章:https://stackoverflow.com/a/51504151/1099519
我不确定这是否是同一情况,因为我没有使用工厂。
使用的版本:.NET Core 3.1和Autofac 4.9.4,用于依赖项注入管理。
使用autofac,您可以注入IEnumerable<IEventHandler<TEvent>>
,而Autofac应该解析其所有实现的列表。
https://autofaccn.readthedocs.io/en/latest/resolve/relationships.html
如question/answer中所示自动注册所有处理程序:
builder.RegisterAssemblyTypes(AppDomain.CurrentDomain.GetAssemblies())
.AsClosedTypesOf(typeof (IEventHandler<>)).AsImplementedInterfaces();
[当您拥有TEvent并想查找所有处理程序时,请通过构造具体的接口类型as follows来获取它们:
Type generic = typeof(IEnumerable<IEventHandler<>>);
Type[] typeArgs = { typeof(TEvent) }; // you might get the Type of TEvent in a different way
Type constructed = generic.MakeGenericType(typeArgs);
您应该将此内容缓存在字典中,以避免在每次事件分发时进行反射。
一旦构造了具体的接口类型,就可以向Autofac要求该接口的所有实现:
var handlers = container.Resolve(constructed);
现在,问题在于,对于处理程序实例,您只能使用Invoke(反射)调用Handle方法。这是一个性能问题,但与您注册和解析处理程序的方式无关。这与您需要从对象调用具体方法这一事实有关。为了避免使用反射,您需要调用具体方法的编译代码(请注意,使用泛型还会为每种类型创建具体的编译代码)。
我可以想到两种获取编译代码来进行此调用的方法:
手动编写一个委托,该委托将您的对象处理程序实例转换为您拥有的每个TEvent类型的具体类型。然后将所有这些委托存储在字典中,以便您可以在运行时基于TEvent类型找到它们,并通过处理程序实例和事件实例对其进行调用。使用这种方法,对于您创建的每个新的TEvent,都需要创建一个匹配的委托。
进行与以前类似的操作,但在启动时发出代码。因此,同样的事情,但是代表的整个创建是自动的。