c# - 动态依赖注入(inject)

标签 c# dynamic dependency-injection

第二种方法

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

例子是:

  1. 日志事件的来源
  2. 计算结果的来源
  3. 系统资源使用的来源
  4. 性能指标来源
  5. ...

插件可以使用这些的任意组合。

示例插件可以是:

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

我要实现的是

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

一个好处是允许插件可选地需要一个变量,例如一个需要 4. 并可选地使用 3.(如果可用)的插件(但在其他情况下也可用)。

第一种方法

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

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

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

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,它的优点是构造函数静态声明每个类的依赖关系。然后使用反射找出您可以创建的内容。

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

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 方法来获取一个可用插件的顺序。

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

关于c# - 动态依赖注入(inject),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35598087/

相关文章:

c# - LINQ to Nhibernate 用户在 where 子句中定义的函数

javascript - 单击单选按钮时必须输入文本框

c# - 当 T 可以是 IEnumerable<T> 时实现 IEquatable<T>

Javafx 网格 Pane ..无法动态添加行..而不收缩

python - 有没有像 python 的通用依赖解析库这样的东西?

c# - jquery 数据表 Ajax-Error/http ://datatables.net/tn/7

c++ - C++中数组的动态调整大小

javascript - Highchart 堆叠条形图无法通过 javascript 工作

java - 一种允许单个 Java 代码库覆盖许多不同版本的方法?

.net - SimpleInjector 不使用隐式属性注入(inject)来注入(inject)属性