【问题标题】:Resolving multiple implementations with generics .net core使用泛型 .net 核心解决多个实现
【发布时间】:2020-01-22 17:29:50
【问题描述】:

我试图弄清楚如何通过 .net 核心中的依赖注入来使用具有泛型的基类的多个实现。

我的基类使用泛型,因此我可以在响应 Dto 中使用不同类型的 List。

在不涉及泛型的情况下,我已经成功使用了许多接口和基类实现。

到目前为止我已经尝试过。

基类

public abstract class GeneratorBase<T>
{
    public abstract ProcessorResponse<T> Process();
}

响应 dto

public class ProcessorResponse<T>
{
    public ProcessorResponse()
    {
        Data = new List<T>();
    }

    public List<T> Data { get; set; }
}

实施编号 1

public class ConfigurationGenerator : GeneratorBase<ConfigurationModel>
{
    public override ProcessorResponse<ConfigurationModel> Process()
    {
        return new ProcessorResponse<ConfigurationModel>();
    }
}

实施编号 2。

public class ApplicationGenerator : GeneratorBase<ApplicationModel>
{
    public override ProcessorResponse<ApplicationModel> Process()
    {
        return new ProcessorResponse<ApplicationModel>();
    }
}

型号

public class ConfigurationModel
{
    public int Count { get; set; }
}

public class ApplicationModel
{
    public string Title { get; set; }
}

我的依赖注入来添加实现。

public static void AddGenerators(this IServiceCollection services)
{
     // add our generators
     services.AddScoped<GeneratorBase<ConfigurationModel>, ConfigurationGenerator>();
     services.AddScoped<GeneratorBase<ApplicationModel>, ApplicationGenerator>();
}

主应用程序这是我发生错误的地方。

public class GeneratorApp
{

    // error because T is not implemented
    private readonly IEnumerable<GeneratorBase> _generators;

    // error because T is not implemented
    public GeneratorApp(IEnumerable<GeneratorBase> generators)
    {
        _generators = generators ?? throw new ArgumentException(nameof(generators));
    }

    public void RunGenerator(string name)
    {
        // get the generator by name and run process
        var generator = _generators.FirstOrDefault(c => c.GetType().Name == name);
        var results = generator.Process();
    }
}

更新 IFoo 示例 有效的 IFoo 示例。

public interface IFoo
{
    string Name { get; }
}

public class Foo1 : IFoo
{
    public string Name => "I'm Foo 1";
}

public class Foo2 : IFoo
{
    public string Name => "I'm Foo 2";
}

依赖注入来添加实现。

public static void AddGenerators(this IServiceCollection services)
{
    // add our Foo's
    services.AddTransient<IFoo, Foo1>();
    services.AddTransient<IFoo, Foo2>();
}

主应用

public class GeneratorApp
{
    private IEnumerable<IFoo> _foos;

    public GeneratorApp(IEnumerable<IFoo> foos)
    {
        _foos = foos;
        RunGenerator("Foo1");
    }

    public void RunGenerator(string name)
    {
        foreach (var foo in _foos)
        {
            Console.WriteLine(foo.Name);
        }

        var foundFoo = _foos.FirstOrDefault(c => c.GetType().Name == name);
        if (foundFoo != null)
        {
            Console.WriteLine(foundFoo.Name);
        }
    }
}

控制台输出

我是 Foo 1

我是 Foo 2

我是 Foo 1

【问题讨论】:

  • 对不起,修剪完成了我的代码,我错过了,它已更新。无论如何,我仍然有这个问题。
  • 我更新了代码,使其更加清晰。 AddGenerators 将添加两个不同的实例。
  • 当您想通过传入您要使用的具体类的实际类名来决定使用哪个生成器时,您误用了依赖注入的概念。您能否解释一下为什么您认为应该使用 di 以及为什么您认为使用具体的类名来选择生成器是个好主意。这两件事实际上是相互矛盾的
  • 在 DI 中,我们可以有许多接口甚至基类的实现。我有很多这些生成器的实现,我不知道用户会使用哪一个,直到他们选择一个。我正在寻找建议。这一切都有效,直到我决定在基类中实现泛型。
  • @KC。 “在 DI 中,我们可以有许多接口甚至基类的实现。” - 是的,但是这些不同的实现是在启动时(或范围初始化)而不是在运行时或交互时选择的。不同的实现旨在用于测试与开发,或用于 A/B 研究/测试等。听起来您只需要工厂模式。

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


【解决方案1】:

基础知识

您误解了依赖注入的目的(和正确用法)。

services.AddScoped<IFoo, Foo>();

用一句话来形容:

如果您要创建的对象的构造函数需要IFoo,请插入Foo 实例。

这就是依赖注入的目的:提供具体对象,即使它们(类的构造函数)要求模糊类型。

它允许类是模糊的,因此不会强烈依赖于任何特定的实现(= 具体类)。


你的问题

简单地说,您的构造函数要求您从未注册过的参数类型 (IEnumerable&lt;GeneratorBase&gt;)。

你只注册了GeneratorBase&lt;ConfigurationModel&gt;GeneratorBase&lt;ApplicationModel&gt;,也就是说你的依赖注入只能解析这两种类型的构造函数参数。否则,DI 框架会抛出异常,因为它不知道如何填写。


解决方案

您似乎想要注入所有(选择的)类型的列表。因此,您必须注册此 exact 类型。例如:

services.AddScoped<IEnumerable<GeneratorBase>>(() => new List<GeneratorBase>()
{
    new ConfigurationGenerator(),
    new ApplicationGenerator()
});

这只是通往可行代码的最短路径。但是,还有进一步的考虑,但您的意图和用例根本不清楚。我强烈建议阅读依赖注入,因为您缺少有关如何有效利用它的关键知识。


脚注:您没有发布 GeneratorBase(非通用)的定义,但您确实引用了此类型。我将假设存在这种类型,而您忘记将其添加到问题中。如果不是,那么还有一些关于泛型的多态性的疑虑,我也建议你复习一下。

【讨论】:

  • 我想我有些不明白。假设我有一个以 Name 作为属性的 IFoo 接口。我可以创建 Foo1 :IFoo 和 Foo2:IFoo。将它们都添加到我的 serviceCollection 中。 services.AddTransient(); services.AddTransient();然后我可以在我的 GeneratorApp 应用程序类中注入一个 IEnumerable _foos 可以获得 IFoo 的所有实例。我可以使用 _foos.GetType().Name == "Foo1" 并获取 Foo1 的实例,执行 _foos.GetType().Name == "Foo2" 并获取 Foo2 的实例。我想对我的 GeneratorBase 做同样的事情。请参阅更新。
  • @KC。 (1) " 然后我可以在我的 GeneratorApp 应用程序类中注入一个 IEnumerable _foos 可以获得 IFoo 的所有实例。" 你不能这样做,除非你在你的DI 框架,这意味着您自己明确选择了IEnumerable&lt;IFoo&gt; 的具体实现。你的 DI 框架不会为你挑选它。
  • @KC.: (2) _foos.GetType().Name == "Foo2" 不是有效代码。我假设你的意思是_foos.Where(foo =&gt; foo.GetType().Name == "Foo2")。是的,你可以这样做,但这真的不是你应该使用依赖注入的方式。您正在创建一个额外的(子)服务提供者(即您自己编写的 IEnumerable),除了这样做之外没有任何技术利益,因为您在技术上可以做到。能够做某事和这是一个好主意有很大的不同。
  • 你看到我上面更新的例子了吗?它在没有注册 IEnumerable 的情况下按描述工作。那个 IEnumerable 是由我假设的依赖框架构造的。
  • @KC.:您更新的代码证明您实际上是在使用 IEnumerable 滚动您自己的服务提供商。不管它是否有效,这都不是一个好方法。这个问题也与依赖注入无关,因为即使您没有使用 DI 容器也会遇到完全相同的问题。正如戴已经建议的那样,查找工厂模式,因为这就是您要寻找的。而且,在我看来,请在泛型中查找多态性,因为您似乎很难理解为什么不能将类型的各种泛型实现放入这样的单个类型化集合中。
猜你喜欢
  • 2017-09-07
  • 2019-04-02
  • 1970-01-01
  • 2017-06-18
  • 1970-01-01
  • 2018-10-17
  • 2017-05-28
  • 2020-08-05
  • 1970-01-01
相关资源
最近更新 更多