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 方法。这是一个性能问题,但与您注册和解析处理程序的方式无关。这与您需要从对象调用具体方法这一事实有关。为避免使用反射,您需要调用具体方法的编译代码(请注意,使用泛型还会为每种类型创建具体的编译代码)。

我可以想到两种方法来获取编译代码来执行此调用:

  1. 手动编写一个委托,将您的对象处理程序实例转换为您拥有的每个 TEvent 类型的具体类型。然后将所有这些委托存储在字典中,以便您可以在运行时根据 TEvent 类型找到它们,并通过处理程序实例和事件实例调用它。使用这种方法,对于您创建的每个新 TEvent,您都需要创建一个匹配的委托。

  2. 做与以前类似的事情,但在启动时发出代码。所以,同样的事情,但是代表的整个创建是自动的。


更新

基于 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 需要一个单独的问题。