Инъекция зависимостей в IEnumerable из Open Generic - PullRequest
0 голосов
/ 03 марта 2020

Я пытаюсь оптимизировать мой код для внедрения списка классов, которые реализуют интерфейс 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; }
}

I зарегистрировать интерфейс маркера IEventHandlerMarker в DI, и при доступе к обработчикам я в настоящее время делаю следующее:

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

Пока все работает, но я бы хотел избавиться от интерфейса маркера и фильтра фильтров c. Поэтому я изменил регистрацию обработчиков следующим способом, и это, кажется, работает должным образом:

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 и наткнулся на следующую статью: { ссылка }

Я не уверен, что это тот же случай, так как я не использую фабрику.

Используемые версии:. NET Core 3.1 и Autofa c 4.9.4 для управления зависимостями.

Ответы [ 2 ]

1 голос
/ 04 марта 2020

Зарегистрируйте все обработчики автоматически, как показано в этом вопрос / ответ :

builder.RegisterAssemblyTypes(AppDomain.CurrentDomain.GetAssemblies())
   .AsClosedTypesOf(typeof (IEventHandler<>)).AsImplementedInterfaces();

Если у вас есть TEvent и вы хотите найти все обработчики, получите их, создав конкретный тип интерфейса следующим образом :

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

Вы должны кэшировать это в словаре, чтобы избежать отражения при каждой отправке события.

Как только вы создадите конкретный тип интерфейса, вы можете спросите у Autofa c обо всех реализациях этого интерфейса:

var handlers = container.Resolve(constructed);

Теперь проблема в том, что с экземплярами обработчика вы можете вызывать метод Handle только с помощью Invoke (отражение). Это проблема производительности, но она не связана с тем, как вы регистрируете и разрешаете обработчики. Это связано с тем, что вам нужно вызывать конкретный метод из объекта. Чтобы избежать использования отражения, вам нужен скомпилированный код, который вызывает конкретный метод (обратите внимание, что использование Generics также создает конкретный скомпилированный код для каждого типа).

Я могу придумать два способа получения скомпилированного кода для выполнения этих вызовов:

  1. Ручная запись делегата, который преобразует экземпляр вашего обработчика объекта в конкретный тип для каждого TEvent. типа что у тебя есть. Затем сохраните все эти делегаты в словаре, чтобы вы могли найти их во время выполнения на основе типа TEvent и вызвать его, передав экземпляр обработчика и экземпляр события. При таком подходе для каждого нового TEvent, который вы создаете, вам нужно создать соответствующий делегат.

  2. Делать то же самое, что и раньше, но с использованием кода при запуске. То же самое, но все создание делегатов - это автомат c.


Обновление

На основе репозитория, совместно используемого 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 требуется отдельный вопрос.

0 голосов
/ 03 марта 2020

С помощью autofa c вы можете ввести IEnumerable<IEventHandler<TEvent>>, и Autofa c должен разрешить список всех его реализаций.

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

...