MEF注入容器
本文关键字:注入 MEF | 更新日期: 2023-09-27 18:19:06
我正在使用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;
}
}
}
这是"Main",合成发生的地方:
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());
}
}
当你得到一把新锤子时,一切都是钉子。
特别是在使用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:
你可以用ref
参数定义Compute
方法:
public IOperand Compute(params IOperand[] operands, ref IOperand result)
{
result.Value = operands.Sum(e => e.Value);
return result;
}
这表示计算方法不负责实例化结果。实际上我喜欢这个版本,因为它在语义上是有意义的,很好处理单元测试,你不需要引用容器。
选项3:
只要使Operand
类在基础设施项目中可访问并实例化它,而不需要容器。我不认为这有什么错,只要你不需要操作数的可扩展性。
还有一点:
在Calculator
类中构建和使用容器。这里也一样:模块不应该知道容器。您应该在最高级别上构建容器,并让它组装所有东西。你的计算器应该只拥有逻辑上需要的属性,甚至不应该知道它们是由容器注入的,是在单元测试中设置的,还是仅仅来自代码(当然除了Import/Export属性)。
最后:
Mike Taulty on MEF (Videos)
这真的很好,我从这些视频中学到了MEF,他也建立了一个计算器:)