Search code examples
c#dependency-injectioninversion-of-controlmef

MEF inject container


I`m building a simple app using MEF to better understand it, and I'm facing a problem. The app is a simple calculator that you can create new operations. Each operation is a class that exports IOperation. Here's the ADD operation class:

[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 is a interface that only exposes a double. The reason for it is that in version 2 you can have a expression like "(2+2)*4")

My problem as you can see is that _container is null when I hit Compute. This concrete class is created when the container composes a [ImportMany(typeof(IOperation))]. So my question is: Is there a way to tell the container who is inverting control to pass a reference of himself to this object?

PS:I would not like to make _container a public property.

Edit1: Here is the only implementation of IOperand so far:

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

This is the "Main", where composition happens:

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());
    }
}

Solution

  • When you get a new hammer, everything is a nail.

    When using DI and MEF in particular, you should always ask yourself, where to use it and where you don't really gain anything. DI doesn't replace normal instantiation completely. In your example: You want to build a modular calculator, where you can extend the set of operators. That makes sense. But do you really need plug-in-ability for buttons? I would just create them in a standard way, one for each operation. Same for operands, does it really make sense to use DI here? I would doubt that, since I suppose there will only be one type of operand in your final program? So you can just make the class accessible and instantiate it wherever you need it.

    When using dependency injection, your 'modules' shouldn't reference the container. What you want is a nice and clean separation of concerns, and the whole thing is called Inversion of Control because you pass the control over object instantiation from the module to the container. If now the module is concerned with asking the container to create the object for itself, you didn't really gain anything.

    Option 1:

    You add a reference to the container anyway and inject it:

    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)
    {...}
    

    Option 2:

    You can define the Compute method with an ref parameter:

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

    This expresses the compute method doesn't feel responsible to instantiate the result. I actually like this version, because it makes sense semantically, is very nice to handle for unit testing and you don't have to reference the container.

    Option 3:

    Just make the Operand class accessible in the infrastructure project and instantiate it without container at all. I don't see anything wrong with that, as far as you don't really need extendability for operands.

    One more remark:

    You build and use the container in the Calculator class. Same here: Your modules shouldn't know the container. You should build the container on the highest level and let it assemble everything. Your calculator should only have the properties it logically needs and should not even know whether they are injected by a container, set in a unit test or just from code (apart from the Import/Export attributes of course).

    And finally:

    Mike Taulty on MEF (Videos)

    This is really good, I kind of learned MEF with these videos and he builds a calculator, too :)