Autofac - 如何将开放泛型注册为命名或键控类型以用于工厂或使用 IIndex<,>

Autofac - how to register open generics as named or keyed types for use in a factory or using IIndex<,>

我正在使用 Net Core 5,并且除了内置的依赖注入之外还不需要使用任何东西。我现在需要能够根据从 UI.

中的服务下拉列表中选择的值来解析特定服务

在所有的 DI 容器中,我决定更深入地研究 Autofac,并认为使用命名服务和工厂模式或键控服务将是可行的方法,但我 运行 有点难度,这里举个例子;

我有一个看起来像这样的界面

public interface IChartDataService<TSource, TTarget>

我有一些服务必须像这样实现这个接口

public class TreantService : IChartDataService<SourceDto, TargetDto>

我正在努力弄清楚应该如何在 Autofac 容器中注册这些服务。 对于命名服务,我试过这样注册

containerBuilder.RegisterGeneric(typeof(MarkOneService)).As(typeof(IChartDataService<,>)).Named<MarkOneService>("markone");

containerBuilder.RegisterGeneric(typeof(MarkTwoService)).As(typeof(IChartDataService<,>)).Named<MarkTwoService>("marktwo");

我试着这样解决它们

var service = scope.ResolveNamed("markone", typeof(IChartDataService<,>));

也试过这种方法

var service = scope.ResolveNamed("markone", typeof(IChartDataService<SourceDto,TargetDto>));

我无法正确注册它们,因为这个错误说服务未注册。

我的首选方法是使用 Keyed 服务,这样我就可以使用 'IIndex' 但这又一次让我困惑如何注册或者我想做的事情是否可行。

我正在尝试像这样注册密钥服务

      containerBuilder.RegisterGeneric(typeof(MarkOneService)).As(typeof(IChartDataService<,>)).Keyed("markone", typeof(IChartDataService<,>));

OR 

containerBuilder.RegisterType<MarkOneService>().As<IChartDataService<,>>().Keyed<IChartDataService<,>>("markone");

我显然遗漏了一些东西,因为我似乎无法获得正确的语法来正确注册。当我确实进行了注册工作时,由于开放接口,我无法解析该服务。

此外,因为我更喜欢使用键控服务,所以我不知道如何才能获得注入的服务字典。这显然不会工作,因为它与我的界面不匹配

IIndex<string, IChartDataService> serviceDic

并且根据 VS

这在语法上是无效的
IIndex<string, typeof(IChartDataService<,>)> serviceDic

OR

IIndex<string, IChartDataService<,>> serviceDic

我现在在这上面花了太多时间,而且我在兜圈子,所以可以对我可能出错的地方做一些更深入的了解,有人有什么建议吗?你能指出我正在努力实现的任何例子吗?

这里有一些要解压的东西,虽然我可以解释为什么事情没有按照你想要的方式工作,但简短的回答是你可能不得不重新设计你做事的方式正在做。这可能不是您想要的答案,但...就是答案。

要了解原因,我们需要暂时完全忽略 Autofac,只看一下您正在使用的类型。

你有一个接口:

public interface IChartDataService<TSource, TTarget>

作为一个开放的泛型,这可以是 IChartDataService<string, Exception> 或者它可以是 IChartDataService<object, object> 或其他任何东西。 (是的,我知道可能会有限制,但请坚持我的看法——作为一个开放的泛型,这些类型可能会在所有地方发生变化。)所以当你以那种形式谈论 IChartDataService<,> 时,这就是你所说的:“开放式泛型,其中任何类型参数点中的任何一个都可以。”

现在,有时 人们会创建一个同样通用的实现,例如:

public class ChartDataService<TSource, TTarget>
  : IChartDataService<TSource, TTarget>

回到 Autofac,that's what RegisterGeneric is for - 处理开放通用 实现 如果你有这种事情,你会这样注册:

builder.RegisterGeneric(typeof(ChartDataService<,>))
       .As(typeof(IChartDataService<,>));

在那种情况下,您实际上是在注册一个开放的泛型。

但是,那不是你所拥有的。你有实现接口的类型——一个非常具体的接口。让我们看看你有什么:

public class TreantService : IChartDataService<SourceDto, TargetDto>

基于此,我们看到:

  • TreantService 根本不是通用的。 class 本身没有泛型类型参数。
  • TreantService 实现了一个 封闭的通用接口 。换句话说,你 可以 TreantService 转换为 IChartDataService<SourceDto, TargetDto> 但你 不能 将其转换为 IChartDataService<string, Exception> 或其他任何内容。

出于所有意图和目的,IChartDataService<SourceDto, TargetDto> 在这种情况下与 IComparable 等任何其他标准接口没有什么不同。一旦它是像这样的封闭泛型,它只是一个类型

假设您有一堆这些,有一堆不同的 DTO 类型:

public class FirstService
  : IChartDataService<FirstSource, FirstTarget> { }
public class SecondService
  : IChartDataService<SecondSource, SecondTarget> { }
public class ThirdService
  : IChartDataService<ThirdSource, ThirdTarget> { }

当然,它们都实现了 IChartDataService<TSource, TTarget>,但它们是封闭的泛型。您不能将它们强制转换为相同的底层接口。

现在假设您想将它们全部存储在 List<T> 中。 T 有什么可以让它发挥作用的?

它将是 object - 他们拥有的唯一共同基础 class。您必须使用 List<object>。是的,这很糟糕,但是正如您发现的那样,没有 List<IChartDataService<,>> 之类的东西,也没有可以将开放泛型作为值的字典。如果你仔细想想,这是有道理的,因为假设你想把它们拉回来:

// Pretend this is a thing.
var list = new List<IChartDataService<,>>();
foreach(var item in  list)
{
  // What type is `item` in this loop?
  // How does the compiler know what types
  // to fill in for the open generic? It's
  // not a dynamic language, so you can't
  // "switch types" on every loop iteration.
}

希望此时您可以开始看到您尝试做的事情存在的一些问题,这不是 Autofac 的问题 - 问题是接口的设计方式以及您希望如何使用它们.

这就是为什么您有时会看到非通用接口,例如 System.IEnumerable 通用接口,例如 System.Generic.IEnumerable<T>IEnumerable 对于常见的事物 不管 泛型类型参数和使事物强类型化的泛型。

我真的不能告诉你如何解决这个问题,因为你如何处理它在很大程度上取决于你的应用程序代码和你到底在做什么。但是,回顾一下我在这里介绍的内容,如果是我的话,我会从这里开始:

  • 忽略 Autofac。尝试设计您可以在单元测试中完全模拟的东西(例如具有可以模拟您实际看到的正确类型的构造函数参数)。如果没有 Autofac 就无法编译或使用所有类型,那么 Autofac 将不会神奇地以某种方式使其工作。
  • 考虑一个通用的非通用接口。有点像 IEnumerableIEnumerable<T> 的区别。可能出于 实现 的目的,拥有该泛型很好,但实际调用的通用方法不需要泛型(或者可能需要 object?) .