我正在使用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());
}
}
答案 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
课程中构建和使用容器。同样在这里:您的模块不应该知道容器。您应该在最高级别构建容器并让它组装所有内容。您的计算器应该只具有逻辑上需要的属性,甚至不应该知道它们是由容器注入,在单元测试中设置还是仅从代码中注入(当然除了导入/导出属性)。
最后:
这真的很好,我有点用这些视频学习MEF,他也建了一个计算器:)