MEF注射容器

时间:2013-06-19 14:31:47

标签: c# dependency-injection inversion-of-control mef

我正在使用MEF构建一个简单的应用程序来更好地理解它,我遇到了一个问题。该应用程序是一个简单的计算器,您可以创建新的操作。每个操作都是一个导出IOperation的类。这是ADD操作类:

[Export(typeof(IOperation))]
internal class Add : IOperation
{
    CompositionContainer _container;

    public string Name
    {
        get { return "Add"; }
    }

    public string Symbol
    {
        get { return "+"; }
    }

    public IOperand Compute(params IOperand[] operands)
    {
        IOperand result = _container.GetExportedValue<IOperand>();
        result.Value = operands.Sum(e => e.Value);
        return result;
    }
}

(IOperand是一个只暴露一个double的接口。原因是在版本2中你可以有一个像“(2 + 2)* 4”这样的表达式

我的问题是,当我点击_container时,Compute为空。当容器组成[ImportMany(typeof(IOperation))]时,将创建此具体类。所以我的问题是:有没有办法告诉容器谁反转控件将自己的引用传递给这个对象?

PS:我不想让_container成为公共财产。

Edit1:到目前为止,这是IOperand的唯一实现:

[Export(typeof(IOperand))]
public class SimpleResult : IOperand
{
    private double _value;
    public double Value
    {
        get
        {
            return _value;
        }
        set
        {
            _value = value;
        }
    }
}

这是组合发生的“主要”:

public class Calculator
{
    [ImportMany(typeof(IOperation))]
    private List<IOperation> _knownOperations;
    private List<ICalculatorButton> _buttons;
    private CompositionContainer _container;

    public Calculator()
    {
        _container = new CompositionContainer(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
        _container.SatisfyImportsOnce(this);
        _buttons = new List<ICalculatorButton>();

        ICalculatorButton button;
        for (int i = 0; i < 10; i++)
        {
            button = _container.GetExportedValue<IDigitButton>();
            button.Symbol = i.ToString();
            ((IDigitButton)button).Value = i;
            _buttons.Add(button);
        }
        foreach (IOperation op in _knownOperations)
        {
            button = _container.GetExportedValue<IOperationButton>();
            button.Symbol = op.Symbol;
            ((IOperationButton)button).Operation = op;
            _buttons.Add(button);
        }
    }

    public IReadOnlyList<IOperation> KnownOperations
    {
        get { return _knownOperations.AsReadOnly(); }
    }

    public IReadOnlyList<ICalculatorButton> Buttons
    {
        get { return _buttons.AsReadOnly(); }
    }

    public IOperand Calculate(IOperation operation, params IOperand[] operands)
    {
        IOperand result = operation.Compute(operands);
        return result;
    }

    public IOperand Calculate(IOperation operation, params double[] operands)
    {
        List<IOperand> res = new List<IOperand>();
        foreach (double item in operands)
        {
            IOperand aux = _container.GetExportedValue<IOperand>();
            aux.Value = item;
            res.Add(aux);
        }
        return Calculate(operation, res.ToArray());
    }
}

1 个答案:

答案 0 :(得分:5)

当你拿到一把新锤子时,一切都是钉子。

特别是在使用DI和MEF时,你应该总是问自己,在哪里使用它以及你什么都没有获得任何东西。 DI并不能完全取代正常的实例化。在您的示例中:您希望构建一个模块化计算器,您可以在其中扩展运算符集。那讲得通。但是你真的需要按钮的插件能力吗?我只是以标准方式创建它们,每个操作一个。操作数相同,在这里使用DI真的有意义吗?我怀疑,因为我想你的最终节目中只有一种操作数?因此,您可以使类可访问并在需要的地方实例化它。

使用依赖注入时,“模块”不应引用容器。你想要的是一个漂亮而干净的关注点分离,整个事情被称为控制反转,因为你将对象实例化的控制从模块传递给容器。如果现在该模块涉及要求容器为自己创建对象,那么你实际上并没有获得任何东西。

选项1:

无论如何都要添加对容器的引用并注入它:

public Calculator()
{
    _container = new CompositionContainer(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
    // This registers the instance _container as CompositionContainer
    _container.ComposeExportedValue<CompositionContainer>(_container);
    _container.SatisfyImportsOnce(this);
    _buttons = new List<ICalculatorButton>();
...


[ImportingConstructor]
public Add(CompositionContainer container)
{...}

选项2:

您可以使用Compute参数定义ref方法:

public IOperand Compute(params IOperand[] operands, ref IOperand result)
{        
    result.Value = operands.Sum(e => e.Value);
    return result;
}

这表示计算方法不负责实例化结果。我实际上喜欢这个版本,因为它在语义上是有意义的,处理单元测试非常好,你不必引用容器。

选项3:

只需在基础结构项目中访问Operand类,并在没有容器的情况下实例化它。我没有看到任何错误,只要你真的不需要操作数的可扩展性。

还有一句话:

您在Calculator课程中构建和使用容器。同样在这里:您的模块不应该知道容器。您应该在最高级别构建容器并让它组装所有内容。您的计算器应该只具有逻辑上需要的属性,甚至不应该知道它们是由容器注入,在单元测试中设置还是仅从代码中注入(当然除了导入/导出属性)。

最后:

Mike Taulty on MEF (Videos)

这真的很好,我有点用这些视频学习MEF,他也建了一个计算器:)