在 C# 中通过依赖注入构建需要类型参数的接口列表

Constructing a List of Interface that requires Type Argument via Dependency Injection in C#

我有这样一份报告:

public class Report : IReport
{
    public PageViewSection PageViews {get;set;}
}

其中有这样定义的部分:

public class PageViewSection : IStateSection
{
}

它使用这样定义的数据处理工厂:

public class PageViewProcessor : IProcessor<PageViewSection>
{
}

我正在尝试在报告数据存储库中使用这些工厂:

public class DataRepository : IParticularReportTypeRepository
{
    private IProcessor[] _processors;
    
    public DataRepository(IProcessor[] processors)
    {
        _processors = processors;
    }
}

是否可以让 DI 在没有类型参数的情况下检索 IProcessor 的多个实例?最终会有数百个 IProcessor 实例,因此为每种处理器类型编写特定接口似乎不合理。我想要一些关于如何最好地做到这一点的架构建议。

编辑:添加IProcessor

public interface IProcessor<TReportSection>
{
    bool ShouldProcess(IncomingData data);
    TReportSection Process(IncomingData data, TReportSection section);
}

自从我发布这篇文章以来,我一直在考虑构建它,以便报告部分能够检索它们的处理器。

处理器在 repo 中的使用方式如下:

var reportSection = await GetReportSectionAsync(evt.EventTime, evt.ContextId);

foreach (var processor in _processors)
{
    processor.Process(evt, reportSection);
}

return await AddOrUpdateDocumentAsync(report);

除非IProcessor<T>实现非通用版本IProcessor,否则不可能将它们全部注入到构造函数中。但即使你这样做,它也会迫使你迭代所有处理器,而只有调用实现 IProcessor<T> 的版本才有意义,其中 T 是所需的类型。

有几种方法可以解决这个问题。这里有两个:

1。使 DataRepository 通用:

例如:

public class DataRepository<TReportSection>
    : IParticularReportTypeRepository<TReportSection>
{
    private IProcessor<TReportSection>[] _processors;
    
    public DataRepository(IProcessor<TReportSection>[] processors)
    {
        _processors = processors;
    }

    public async Task<TReportSection> Process(IncomingData evt)
    {
        TReportSection reportSection = ...;

        foreach (var processor in _processors)
        {
            processor.Process(evt, reportSection);
        }

        ...
    }
}

使用这种方法,您可以将特定的 IParticularReportTypeRepository<TReportSection>(例如 IParticularReportTypeRepository<PageViewSection> 注入到消费者的构造函数中。

这种设计只有在每个消费者只有一个(或几种类型的报告部分)传递时才有可能。否则,消费者会在其构造函数中获得许多 IParticularReportTypeRepository<T> 类型的依赖项。

如果您需要更动态的方法,您可以开始在 DataRepository 中使用反射并(可选)回调使用的 DI 容器。接下来演示。

2。从内部解析集合 DataRepository:

例如:

public class DataRepository : IParticularReportTypeRepository
{
    private readonly Container _container;
    
    public DataRepository(Container container)
    {
        _container = container;
    }

    public async Task<IStateSection> Process(IncomingData evt)
    {
        IStateSection reportSection =
            await GetReportSectionAsync(evt.EventTime, evt.ContextId);
            
        Type processorType =
            typeof(IProcessor<>).MakeGenericType(reportSection.GetType())
        
        IEnumerable<object> processors =
            _container.GetAllInstances(processorType);

        // NOTE the dynamic keywords here.
        foreach (dynamic processor in processors)
        {
            processor.Process(evt, (dynamic)reportSection);
        }

        ...
    }
}

使用此实现,DataRepository 仅在调用其 Process 方法期间知道它需要什么类型的处理器。它通过构建 IProcessor<T> 的封闭通用版本并要求 DI 容器 return 它们的列表来实现。请注意,API 调用可能看起来不同,具体取决于您使用的 DI 容器。您应该查看容器的文档以了解如何解决集合问题。如果您不使用 DI 容器,则可能需要注入 Func<Type, IEnumerable<object>> 工厂委托。

IMPORTANT: Because of the dependency on the DI Container, this class should now be part of your Composition Root.

DataRepository.Process 方法利用反射并使用 C# 的 dynamic 关键字调用处理器。这本身并不是最好的方法(它有一些缺点),但它产生的代码量最少,这对于演示目的很有用。 Jimmy Bogard 最近 did an article 演示了有关泛型类型调用的两种不同方法。您可能想看看那篇文章。