ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

C#-动态依赖注入

2019-10-27 09:05:21  阅读:180  来源: 互联网

标签:dependency-injection dynamic c


第二种方法

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

例如:

>日志事件的来源
>计算结果来源
>系统资源使用情况的来源
>绩效指标来源
> …

插件可以使用这些的任何组合.

示例插件可以是:

>使用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[]) }; }
    }
}

解决方法:

把事情简单化.让插件依赖于Constructor Injection,它的优点是构造函数静态地声明每个类的依赖关系.然后使用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需要IFoo,Plugin2需要IBar和IBaz.第三类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方法来获取一系列可用的插件.

您可以轻松地扩展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();
    }
}

标签:dependency-injection,dynamic,c
来源: https://codeday.me/bug/20191027/1943452.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有