动态依赖注入

时间:2016-02-24 09:25:31

标签: c# dynamic dependency-injection

第二种方法

我有一系列应用程序提供可扩展(即不固定)的变量集,可供各种插件使用。

例如:

  1. 日志事件的来源
  2. 计算结果的来源
  3. 系统资源使用来源
  4. 绩效指标的来源
  5. ...
  6. 插件可以使用这些的任意组合。

    示例插件可以是:

    • 自定义错误记录器,使用1。
    • 自定义统计模块,使用2。
    • 使用3.和4的性能工具。

    我想要实现的是

    • 提供了一个插件列表,可以在给定此应用程序中存在的一组变量的情况下使用这些插件(当没有日志事件源时,您应该无法选择自定义错误记录器。)
    • 使用简单且安全的方式将变量传递给插件,这样就不会因为缺少变量而导致运行时错误。

    奖励是允许插件可选地需要变量,例如一个需要4的插件,并且可选地使用3.如果可用(但也可以使用)。

    第一种方法

    我想实现某种“动态依赖注入”。 让我用一个用例来解释它。

    我正在构建一组将用于一系列应用程序的库。 每个应用程序都可以提供一组不同的变量,这些变量可供需要这些变量的某些“处理程序”使用。 根据具体的可用变量,必须确定可用处理程序的数量,因为只有在可以访问所有必需变量的情况下才能使用处理程序。 此外,我正在寻找一种方法,使调用尽可能安全。 编译时可能不可能,但“检查一次,之后永远不会失败”就可以了。

    下面是第一个草图。在这个阶段,一切都还可以改变。

    class DynamicDependencyInjectionTest
    {
        private ISomeAlwaysPresentClass a;
        private ISomeOptionalClass optionA;
        private ISomeOtherOptionalClass optionB;
        private ISomeMultipleOption[] multi;
    
        private IDependentFunction dependentFunction;
    
        void InvokeDependency()
        {
            // the number of available dependencies varies.
            // some could be guaranteed, others are optional, some maybe have several instances
            var availableDependencies = new IDependencyBase[] {a, optionA, optionB}.Concat(multi).ToArray();
            //var availableDependencies = new IDependencyBase[] { a  };
            //var availableDependencies = new IDependencyBase[] { a, optionA }.ToArray();
            //var availableDependencies = new IDependencyBase[] { a, optionB }.ToArray();
            //var availableDependencies = new IDependencyBase[] { a , multi.First() };
    
            //ToDo
            // this is what I want to do
            // since we checked it before, this must always succeed
            somehowInvoke(dependentFunction, availableDependencies);
    
        }
    
        void SetDependentFunction(IDependentFunction dependentFunction)
        {
            if (! WeCanUseThisDependentFunction(dependentFunction))
                throw new ArgumentException();
    
            this.dependentFunction = dependentFunction;
        }
    
        private bool WeCanUseThisDependentFunction(IDependentFunction dependentFunction)
        {
            //ToDo
            //check if we can fulfill the requested dependencies
            return true;
        }
    
    
        /// <summary>
        /// Provide a list which can be used by the user (e.g. selected from a combobox)
        /// </summary>
        IDependentFunction[] AllDependentFunctionsAvailableForThisApplication()
        {
            IDependentFunction[] allDependentFunctions = GetAllDependentFunctionsViaReflection();
            return allDependentFunctions.Where(WeCanUseThisDependentFunction).ToArray();
        }
    
        /// <summary>
        /// Returns all possible candidates
        /// </summary>
        private IDependentFunction[] GetAllDependentFunctionsViaReflection()
        {
            var types = Assembly.GetEntryAssembly()
                .GetTypes()
                .Where(t => t.IsClass && typeof (IDependentFunction).IsAssignableFrom(t))
                .ToArray();
    
            var instances = types.Select(t => Activator.CreateInstance(t) as IDependentFunction).ToArray();
            return instances;
        }
    
    
        private void somehowInvoke(IDependentFunction dependentFunction, IDependencyBase[] availableDependencies)
        {
            //ToDo
        }
    }
    
    // the interfaces may of course by changed!
    
    /// <summary>
    /// Requires a default constructor
    /// </summary>
    interface IDependentFunction
    {
        void Invoke(ISomeAlwaysPresentClass a, IDependencyBase[] dependencies);
        Type[] RequiredDependencies { get; }
    }
    
    interface IDependencyBase { }
    interface ISomeAlwaysPresentClass : IDependencyBase { }
    interface ISomeOptionalClass : IDependencyBase { }
    interface ISomeOtherOptionalClass : IDependencyBase { }
    interface ISomeMultipleOption : IDependencyBase { }
    
    
    class BasicDependentFunction : IDependentFunction
    {
        public void Invoke(ISomeAlwaysPresentClass a, IDependencyBase[] dependencies)
        {
            ;
        }
    
        public Type[] RequiredDependencies
        {
            get { return new[] {typeof(ISomeAlwaysPresentClass)}; }
        }
    }
    
    class AdvancedDependentFunction : IDependentFunction
    {
        public void Invoke(ISomeAlwaysPresentClass a, IDependencyBase[] dependencies)
        {
            ;
        }
    
        public Type[] RequiredDependencies
        {
            get { return new[] { typeof(ISomeAlwaysPresentClass), typeof(ISomeOptionalClass) }; }
        }
    }
    
    class MaximalDependentFunction : IDependentFunction
    {
        public void Invoke(ISomeAlwaysPresentClass a, IDependencyBase[] dependencies)
        {
            ;
        }
    
        public Type[] RequiredDependencies
        {
            // note the array in the type of ISomeMultipleOption[]
            get { return new[] { typeof(ISomeAlwaysPresentClass), typeof(ISomeOptionalClass), typeof(ISomeOtherOptionalClass), typeof(ISomeMultipleOption[]) }; }
        }
    }
    

2 个答案:

答案 0 :(得分:4)

保持简单。让插件依赖构造函数注入,这样做的好处是构造函数静态地宣告每个类的依赖关系。然后使用Reflection来弄清楚你可以创建什么。

例如,假设您有三个服务:

public interface IFoo { }

public interface IBar { }

public interface IBaz { }

此外,假设存在三个插件:

public class Plugin1
{
    public readonly IFoo Foo;

    public Plugin1(IFoo foo)
    {
        this.Foo = foo;
    }
}

public class Plugin2
{
    public readonly IBar Bar;
    public readonly IBaz Baz;

    public Plugin2(IBar bar, IBaz baz)
    {
        this.Bar = bar;
        this.Baz = baz;
    }
}

public class Plugin3
{
    public readonly IBar Bar;
    public readonly IBaz Baz;

    public Plugin3(IBar bar)
    {
        this.Bar = bar;
    }

    public Plugin3(IBar bar, IBaz baz)
    {
        this.Bar = bar; ;
        this.Baz = baz;
    }
}

很明显,Plugin1需要IFooPlugin2需要IBarIBaz。第三个类Plugin3稍微有点特殊,因为它有一个可选的依赖项。虽然需要 IBar,但如果它可用,它也可以使用IBaz

您可以定义一个使用一些基本反射的Composer来检查是否可以根据可用服务创建各种插件的实例:

public class Composer
{
    public readonly ISet<Type> services;

    public Composer(ISet<Type> services)
    {
        this.services = services;
    }

    public Composer(params Type[] services) :
        this(new HashSet<Type>(services))
    {
    }

    public IEnumerable<Type> GetAvailableClients(params Type[] candidates)
    {
        return candidates.Where(CanCreate);
    }

    private bool CanCreate(Type t)
    {
        return t.GetConstructors().Any(CanCreate);
    }

    private bool CanCreate(ConstructorInfo ctor)
    {
        return ctor.GetParameters().All(p => 
            this.services.Contains(p.ParameterType));
    }
}

如您所见,您使用一组可用服务配置Composer实例,然后可以使用候选列表调用GetAvailableClients方法以获取一系列可用插件。< / p>

您可以轻松扩展Composer类,以便能够创建所需插件的实例,而不是只告诉您哪些插件可用。

您可能会在某些DI容器中找到此功能。 IIRC,Castle Windsor公开了一个 Tester / Doer API,如果MEF也支持这样的功能,我也不会感到惊讶。

以下xUnit.net参数化测试表明上述Composer有效。

public class Tests
{
    [Theory, ClassData(typeof(TestCases))]
    public void AllServicesAreAvailable(
        Type[] availableServices,
        Type[] expected)
    {
        var composer = new Composer(availableServices);
        var actual = composer.GetAvailableClients(
            typeof(Plugin1), typeof(Plugin2), typeof(Plugin3));
        Assert.True(new HashSet<Type>(expected).SetEquals(actual));
    }
}

internal class TestCases : IEnumerable<Object[]>
{
    public IEnumerator<object[]> GetEnumerator()
    {
        yield return new object[] {
            new[] { typeof(IFoo), typeof(IBar), typeof(IBaz) },
            new[] { typeof(Plugin1), typeof(Plugin2), typeof(Plugin3) }
        };
        yield return new object[] {
            new[] { typeof(IBar), typeof(IBaz) },
            new[] { typeof(Plugin2), typeof(Plugin3) }
        };
        yield return new object[] {
            new[] { typeof(IFoo), typeof(IBaz) },
            new[] { typeof(Plugin1) }
        };
        yield return new object[] {
            new[] { typeof(IFoo), typeof(IBar) },
            new[] { typeof(Plugin1), typeof(Plugin3) }
        };
        yield return new object[] {
            new[] { typeof(IFoo) },
            new[] { typeof(Plugin1) }
        };
        yield return new object[] {
            new[] { typeof(IBar) },
            new[] { typeof(Plugin3) }
        };
        yield return new object[] {
            new[] { typeof(IBaz) },
            new Type[0]
        };
        yield return new object[] {
            new Type[0],
            new Type[0]
        };
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

答案 1 :(得分:0)

经验教训:

  1. 最初我以为我需要一个per调用方法注入,这很复杂,因为:

    • 在实例化类之前,更难判断依赖关系是否可以实现
    • 方法签名不是完全类型安全的,也不是(非通用)接口的一部分。
    • 虽然它可能是非标准的,因此现有系统不易支持
  2. 然后我通过添加所需的依赖项作为属性切换到一种属性注入,每个属性绑定到一个接口,以便可以轻松地发现所需的依赖项。调用本身是无参数的。

    • 这样可以更容易地找出可以使用的插件。
    • &#34;如果此插件实现了用于设置此依赖项属性的接口,则设置它&#34;语句可以在调用无参数方法之前填写依赖项。
    • 如果省略了一个调用,则尚未设置依赖项。
  3. 可能正确的方法是使用构造函数注入,因此可以使用标准工具。调用本身也是无参数的,因此它非常适合接口。

  4. 以下是Mark解决方案的更完整版本,包括解析组件。它使用Castle.Windsor,xUnit,Shouldly和Resharper的NotNull, CanBeNull属性。

    通过注入一个解析工具工厂来删除对Castle.Windsor的直接依赖是必要的(因为它必须接受来自主机的实例,我们不能直接传入解析器)。

    public interface IFoo { }
    
    public interface IBar { }
    
    public interface IBaz { }
    
    /// <summary>
    /// Needed to invoke the plugin
    /// </summary>
    public interface IPlugin
    {
        void Invoke();
    }
    
    public class Plugin1 : IPlugin
    {
        public readonly IFoo Foo;
    
        public Plugin1([NotNull] IFoo foo)
        {
            if (foo == null) throw new ArgumentNullException("foo");
            this.Foo = foo;
        }
    
        public void Invoke()
        {
            ;
        }
    }
    
    public class Plugin2 : IPlugin
    {
        public readonly IBar Bar;
        public readonly IBaz Baz;
    
        public Plugin2([NotNull] IBar bar, [NotNull] IBaz baz)
        {
            if (bar == null) throw new ArgumentNullException("bar");
            if (baz == null) throw new ArgumentNullException("baz");
            this.Bar = bar;
            this.Baz = baz;
        }
        public void Invoke()
        {
            ;
        }
    }
    
    public class Plugin3 : IPlugin
    {
        public readonly IBar Bar;
        public readonly IBaz Baz;
    
        public Plugin3([NotNull] IBar bar, [CanBeNull] IBaz baz = null)
        {
            if (bar == null) throw new ArgumentNullException("bar");
            this.Bar = bar; ;
            this.Baz = baz;
        }
        public void Invoke()
        {
            ;
        }
    }
    
    public class Bar : IBar
    {
    }
    
    public class SampleHostTest
    {
    
    
        [Fact]
        void SampleHostCanResolvePlugin3ButNot1And2()
        {
    
            var bar = new Bar();
            var plugins = Assembly.GetAssembly(typeof(SampleHost))
                    .GetTypes()
                    .Where(t => t.IsClass && typeof(IPlugin).IsAssignableFrom(t))
                    .ToArray();
    
            var sut = new SampleHost(bar, plugins);
    
            sut.IsPluginSupported(typeof(Plugin1)).ShouldBeFalse();
            sut.IsPluginSupported(typeof(Plugin2)).ShouldBeFalse();
            sut.IsPluginSupported(typeof(Plugin3)).ShouldBeTrue();
        }
    
        [Fact]
        void ResolvePlugin3()
        {
    
            var bar = new Bar();
            var plugins = Assembly.GetAssembly(typeof(SampleHost))
                    .GetTypes()
                    .Where(t => t.IsClass && typeof(IPlugin).IsAssignableFrom(t))
                    .ToArray();
    
            var sut = new SampleHost(bar, plugins);
    
            sut.IsPluginSupported(typeof(Plugin3)).ShouldBeTrue();
    
            sut.CreateAndInvokePlugin(typeof(Plugin3));
            // no exception => succeeded
        }
    
    
    
    }
    
    public class SampleHost
    {
        private readonly IBar bar;
        private readonly IWindsorContainer container;
        private Type[] plugins;
    
        public SampleHost(IBar bar, IEnumerable<Type> plugins)
        {
            this.bar = bar;
            this.plugins = plugins.ToArray();
            this.container = new WindsorContainer();
            container.Register(Component.For<IBar>().Instance(this.bar));
    
            foreach (var plugin in this.plugins)
            {
                container.Register(Component.For(plugin).ImplementedBy(plugin).LifestyleTransient());
            }
        }
    
        public bool IsPluginSupported(Type type)
        {
            var result = container.Kernel.HasComponent(type) &&
                         container.Kernel.GetHandler(type).CurrentState == HandlerState.Valid;
            return result;
        }
    
        public void CreateAndInvokePlugin(Type type)
        {
            Assert.True(IsPluginSupported(type));
    
            var plugin = container.Resolve(type)as IPlugin;
    
            Debug.Assert(plugin != null, "plugin != null");
            plugin.Invoke();
        }
    
    
    }