【问题标题】:Dependency Injection of IEnumerable of Open GenericOpen Generic的IEnumerable的依赖注入
【发布时间】:2020-06-15 13:24:03
【问题描述】:

我正在尝试优化我的代码以注入一个类列表,这些类实现一个接口 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&lt;&gt;,如下所示:

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);
}

但结果总是为空。 我在 Stackoverflow 上搜索并查看了一些文章,发现了以下文章: https://stackoverflow.com/a/51504151/1099519

我不确定这是不是同样的情况,因为我没有使用工厂。

使用的版本:.NET Core 3.1 和 Autofac 4.9.4 用于依赖注入管理。

【问题讨论】:

  • 除非我遗漏了什么,否则 Autofac 会自行完成您需要的一切。检查这个问题:stackoverflow.com/questions/16757945/…
  • 我没有问题,注册课​​程时,这似乎工作正常 - 我的问题是,我无法从 ServiceProvider 访问它们。
  • chapter 15DIPP&P 中有一个关于在MS.DI 中注册泛型类型的广泛讨论。例如,看一下this 部分。然而,该章的结论是,MS.DI 受到严重限制,使得这类场景非常难以实现。
  • 我同意@Steven,MS DI 肯定达不到 Autofac 的水平。然而,它比 Autofac 更轻巧,可以满足许多不需要 Autofac 等重量级的需求
  • @DominikAmon 我不清楚您要达到的目标。您是否需要解析所有 IEventHandler,而不管 TEvent 是什么,还是只需要为您发布的 TEvent 解析?它们有两种不同的解决方案。另外,您说注册类没有问题,但是对于 Autofac 在一行代码中所做的事情似乎非常冗长。最后,您是否考虑过将匹配的 TEvents 与 IEventHandlers 缓存在字典中?对每个事件派发进行反射将非常低效。

标签: c# generics .net-core dependency-injection autofac


【解决方案1】:

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 类中

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 }));
        }
    }
}

请注意,如原始答案中所述,此解决方案不包括非常必要的性能优化。应该做的最低限度是缓存所有类型和 MethodInfos,这样就不需要每次都构造它们。如前所述,第二个优化是避免使用 Invoke,但实现起来更复杂,而且 IMO 需要一个单独的问题。

【讨论】:

  • 我的同事在 Github 上创建了一个项目来重现:github.com/Lutti1988/effective-di 没有得到它的工作?
  • 你推动了整个解决方案吗?如果我在存储库中搜索“MakeGenericType”,它不会找到它。
  • 我们尝试了几种方法,但也许我们确实误会了一些东西。这是最新的提交:github.com/Lutti1988/effective-di/commit/…
  • 好的,我看到代码需要调整才能正常工作。我会试着为你制作一个样本。
  • 感谢您的更新!我现在看到了你的意思,我没有考虑通过 IEvent 的具体实现在 Emit 方法中获取事件的类型,因为我只关注构造函数。它就像一个魅力:)
【解决方案2】:

使用 autofac,您可以注入 IEnumerable&lt;IEventHandler&lt;TEvent&gt;&gt;,Autofac 应该解析它的所有实现的列表。

https://autofaccn.readthedocs.io/en/latest/resolve/relationships.html

【讨论】:

  • 由于 TEvent 是泛型的,Autofac 的调用应该如何?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-18
  • 1970-01-01
  • 2016-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多