Open Generic IEnumerable 的依赖注入
Dependency Injection of IEnumerable of Open Generic
我正在尝试优化我的代码以注入 类 列表,该列表实现了一个接口
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);
}
但结果总是为空。
我用谷歌搜索并查看了一些关于 Whosebug 的文章,发现了以下文章:
我不确定这是否是同一种情况,因为我没有使用工厂。
使用的版本:.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,您都需要创建一个匹配的委托。
做与以前类似的事情,但在启动时发出代码。所以,同样的事情,但是代表的整个创建是自动的。
更新
基于 OP 共享的存储库,我创建了一个工作版本。解析处理程序并调用它们的主要代码位于 EventPublisherService
class
public class EventPublisherService : IEventPublisherService
{
private readonly ILifetimeScope _lifetimeScope;
public EventPublisherService(ILifetimeScope lifetimeScope)
{
_lifetimeScope = lifetimeScope;
}
public async Task Emit(IEvent eventItem)
{
Type[] typeArgs = { eventItem.GetType() };
Type handlerType = typeof(IEventHandler<>).MakeGenericType(typeArgs);
Type handlerCollectionType = typeof(IEnumerable<>).MakeGenericType(handlerType);
var handlers = (IEnumerable<object>)_lifetimeScope.Resolve(handlerCollectionType);
var handleMethod = handlerType.GetMethod("Handle");
foreach (object handler in handlers)
{
await ((Task)handleMethod.Invoke(handler, new object[] { eventItem }));
}
}
}
请注意,如原始答案中所述,此解决方案不包括非常必要的性能优化。至少应该做的是缓存所有类型和 MethodInfo,这样就不需要每次都构造它们。如前所述,第二个优化是避免使用 Invoke,但实现起来更复杂,IMO 需要一个单独的问题。
我正在尝试优化我的代码以注入 类 列表,该列表实现了一个接口
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);
}
但结果总是为空。
我用谷歌搜索并查看了一些关于 Whosebug 的文章,发现了以下文章:
我不确定这是否是同一种情况,因为我没有使用工厂。
使用的版本:.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,您都需要创建一个匹配的委托。
做与以前类似的事情,但在启动时发出代码。所以,同样的事情,但是代表的整个创建是自动的。
更新
基于 OP 共享的存储库,我创建了一个工作版本。解析处理程序并调用它们的主要代码位于 EventPublisherService
class
public class EventPublisherService : IEventPublisherService
{
private readonly ILifetimeScope _lifetimeScope;
public EventPublisherService(ILifetimeScope lifetimeScope)
{
_lifetimeScope = lifetimeScope;
}
public async Task Emit(IEvent eventItem)
{
Type[] typeArgs = { eventItem.GetType() };
Type handlerType = typeof(IEventHandler<>).MakeGenericType(typeArgs);
Type handlerCollectionType = typeof(IEnumerable<>).MakeGenericType(handlerType);
var handlers = (IEnumerable<object>)_lifetimeScope.Resolve(handlerCollectionType);
var handleMethod = handlerType.GetMethod("Handle");
foreach (object handler in handlers)
{
await ((Task)handleMethod.Invoke(handler, new object[] { eventItem }));
}
}
}
请注意,如原始答案中所述,此解决方案不包括非常必要的性能优化。至少应该做的是缓存所有类型和 MethodInfo,这样就不需要每次都构造它们。如前所述,第二个优化是避免使用 Invoke,但实现起来更复杂,IMO 需要一个单独的问题。