C# 在处理插件时,从组合根目录之外的DI容器解析是否可以?

C# 在处理插件时,从组合根目录之外的DI容器解析是否可以?,c#,plugins,dependency-injection,simple-injector,di-containers,C#,Plugins,Dependency Injection,Simple Injector,Di Containers,我正在构建一个WPF(桌面)应用程序,利用依赖注入、DI容器和。我的应用程序在启动期间还从单独的程序集中加载插件,并且这些插件在DI容器中注册。我在启动时在composition root中解析了一次整个对象图,但是,我在解析插件时遇到了一些问题,我想知道是否可以将DI容器注入工厂来解析未知类型? 我的插件全部: 实现一个公共接口,IPlugin 需要是暂时的(因为多个实例可以同时存在) 依赖于运行时值在初始化时对其进行配置 可能会,也可能不会,通过构造函数注入其他依赖项 我已经开始实现一个

我正在构建一个WPF(桌面)应用程序,利用依赖注入、DI容器和。我的应用程序在启动期间还从单独的程序集中加载插件,并且这些插件在DI容器中注册。我在启动时在composition root中解析了一次整个对象图,但是,我在解析插件时遇到了一些问题,我想知道是否可以将DI容器注入工厂来解析未知类型?

我的插件全部:

  • 实现一个公共接口,
    IPlugin
  • 需要是暂时的(因为多个实例可以同时存在)
  • 依赖于运行时值在初始化时对其进行配置
  • 可能会,也可能不会,通过构造函数注入其他依赖项
我已经开始实现一个
PluginFactory
来在需要时初始化插件,因为:“任何需要运行时值来构造特定依赖项的地方,抽象工厂都是解决方案。”

但是,我不能在我的工厂中只使用
new()
插件,因为我不知道它们的类型和依赖性

我想到了几种解决办法:

  • 我可以将DI容器注入到
    PluginFactory
    的构造函数中,在运行时解析插件并将其终止。然而,这与存款准备金率模式背道而驰


  • 我可以通过
    PluginFactory
    的构造函数注入
    IEnumerable插件。但是,这违反了瞬态要求,因为每个实例只有一个副本[*错误]。我可以通过在我所有的插件上实现
    ICloneable
    并在工厂中克隆它们来解决这个问题,但是,这既很难纠正依赖项,又会使所有插件变得混乱

  • [*错误]编辑:注意,根据所选答案,这是错误的!因此,选项#2是最好的选项,只要您将插件的生存期注册为瞬态。

  • 我可以将插件类型而不是实例注入
    PluginFactory
    并使用
    Activator.CreateInstance()
    创建实例。我甚至可以为插件定义一个公共构造函数签名,并用依赖项初始化它们。然而,这违反了我的要求,即插件具有不同的依赖关系,这也导致了错误

  • 最后,以3为基础。我可以注入插件类型并使用反射来整理插件的依赖关系。然而,这听起来像是一个很大的工作,让我想知道我是在尝试构建还是复制DI容器中的解析方法。后者是我绝对不想做的事情,也不想看到问题的症结所在

  • 因此,我选择了偏好1,尽管这与RRR模式背道而驰


    实例化我的插件的最佳方式是什么(我倾向于#1)。我是否完全错过了一些选择?或者我可能误判了我的其他选择吗?

    既然你已经提到了马克·希曼,请看看他的文章和他的文章。长话短说,可以将容器引用/注入到构成根的基础结构组件中


    因此,解决方案是定义一个
    IPluginFactory
    抽象,并将其放在应用程序的核心库中。在您的组合根目录中(注意:将CR视为一个层,而不是一个类),您可以为此抽象创建一个实现,在这里可以注入容器。

    在Simple Injector中,注入的
    IEnumerable
    实例表现为流。这意味着每次迭代可枚举项时,都会再次向容器请求实例。各国:

    注意:简单注入器保留返回实例的生活方式 从注入的
    IEnumerable
    实例。事实上,你不应该这样做 请将插入的
    IEnumerable
    作为 实现应该视为实例的强>流< /强>。简单的 Injector将始终向同一个流注入一个引用(
    IEnumerable
    本身就是一个单例)并且每次迭代
    IEnumerable
    ,对于每个单独的组件,都会询问容器 根据该组件的生活方式解析实例。 不管[consuming component]注册为 singleton,它包装的验证器每个都有自己的特定 生活方式

    容器将根据实例的生活方式返回实例。如果实例注册为transient,则意味着每次迭代集合时都会得到一个新实例。从某种意义上说,由简单注入器注入的可枚举项表现为工厂。这里有一个小测试显示了这种行为:

    [TestMethod]
    public void EnumerablesBehaveAsStreams()
    {
        // Arrange
        var container = new Container();
    
        container.Collection.Register<ILogger>(typeof(SqlLogger), typeof(FileLogger));
    
        IEnumerable<ILogger> loggers = container.GetAllInstances<ILogger>();
    
        // Act
        ILogger sqlLogger1 = loggers.First();
        ILogger sqlLogger2 = loggers.First();
    
        // Assert
        Assert.AreNotSame(sqlLogger1, sqlLogger2);
    }
    
    现在集合中的第一个插件注册为singleton,而其他插件仍然是暂时的。如果您想以相同的生活方式注册他们,您可以执行以下操作:

    List pluginTypes=LoadPluginTypes();
    pluginTypes.ForEach(type=>container.Register(type,lifety.Singleton));
    容器、集合、注册(插件类型);
    
    另一个选项是将
    注册
    实例的集合传递到
    集合中。注册
    方法:

    List pluginTypes=LoadPluginTypes();
    容器。收集。登记(类型(IPlugin),
    从输入插件类型
    选择Lifestyle.Singleton.CreateRegistration(
    类型(IPlugin)、类型、容器);
    
    在这种情况下,以前的注册之间没有实际的区别,但是当您想做更高级的事情时,注册
    注册
    实例变得很有趣
    List<Type> pluginTypes = LoadPluginTypes();
    
    container.Collection.Register<IPlugin>(pluginTypes);
    
    public class PluginFactory : IPluginFactory
    {
        private readonly IEnumerable<IPlugin> plugins;
    
        public PluginFactory(IEnumerable<IPlugin> plugins)
        {
            this.plugins = plugins;
        }
    
        public IPlugin GetPluginByName(string name)
        {
            return this.plugins.Single(plugin => plugin.Name == name);
        }
    }