在 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 演示了有关泛型类型调用的两种不同方法。您可能想看看那篇文章。
我有这样一份报告:
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 演示了有关泛型类型调用的两种不同方法。您可能想看看那篇文章。