Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IoC, Dependency injection and constructor arguments

I have a service that I want to be able to create according to the Inversion of Control principle so I have created an interface and a service class.

public interface IMyService
{
    void DoSomeThing1();
    void DoSomeThing2();
    void DoSomeThing3();
    string GetSomething();

}

public class MyService : IMyService
{
    int _initialValue;
    //...

    public MyService(int initialValue)
    {
        _initialValue = initialValue;
    }

    public void DoSomeThing1()
    {
        //Do something with _initialValue
        //...
    }

    public void DoSomeThing2()
    {
        //Do something with _initialValue
        //...
    }

    public void DoSomeThing3()
    {
        //Do something with _initialValue
        //...
    }

    public string GetSomething()
    {
        //Get something with _initialValue
        //...
    }
}

With for example Unity I can set up my IoC.

public static class MyServiceIoc
{
    public static readonly IUnityContainer Container;

    static ServiceIoc()
    {
        IUnityContainer container = new UnityContainer();
        container.RegisterType<IMyService, MyService>();
        Container = container;
    }
}

The problem is the constructor parameter. I could use a ParameterOverride like

var service = MyServiceIoc.Container.Resolve<IMyService>(new ParameterOverrides
{                                                                                   
    {"initialValue", 42}
});

But I don't want to use losely typed parameters. What if someone changes the constructor parameter name or adds one parameter? He won't be warned at comple-time and maybe no one will detect it but the end user. Maybe the programmer changes he IoC setup for the tests, but forgets it for the "release" usage, then not even a codebase with 100% code coverage will detect the run-time error.

One could add an Init-function to the interface and service, but then the user of the service have to understand that and remember to call the init function every time he gets an instance of the service. The service becomes less self explanetory and open for incorrect usage. I'ts best if methods are not dependent on which order they are called.

One way to make it a little safer would be to have a Create-function on the Ioc.

public static class MyServiceIoc
{
    //...
    public IMyService CreateService(int initialValue)
    {
        var service = Container.Resolve<IMyService>();
        service.Init(initialValue);

    }
}

But the concerns mentioned above still applies if you only look at the service and its interface.

Does anyone have an robust solution to this problem? How can I pass an initial value to my service in a safe way still using IoC?

like image 853
AxdorphCoder Avatar asked Oct 07 '15 13:10

AxdorphCoder


2 Answers

A DI Container is reflection-based, and fundamentally weakly typed. The problem is much broader than with Primitive Dependencies - it's present everywhere.

As soon as you do something like the following, you've already lost compile-time safety:

IUnityContainer container = new UnityContainer();
container.RegisterType<IMyService, MyService>();
var service = container.Resolve<IMyService>(new ParameterOverrides
{                                                                                   
    {"initialValue", 42}
});

The problem is that you can remove the second statement, and the code still compiles, but now it'll no longer work:

IUnityContainer container = new UnityContainer();
var service = container.Resolve<IMyService>(new ParameterOverrides
{                                                                                   
    {"initialValue", 42}
});

Notice that the lack of compile-time safety has nothing to do with the Concrete Dependency, but with the fact that a DI Container is involved.

This isn't a Unity problem either; it applies to all DI Containers.

There are cases where a DI Container may make sense, but in most cases, Pure DI is a simpler and safer alternative:

IMyService service = new MyService(42);

Here, you'll get a compiler error if someone else changes the API while you're looking away. That's good: compiler errors give you more immediate feedback than run-time errors.


As an aside, when you pass in a Primitive Dependency and invisibly turn it into a Concrete Dependency, you make it more difficult for the client to understand what's going on.

I'd recommend designing it like this instead:

public class MyService : IMyService
{
    AnotherClass _anotherObject;
    // ...

    public MyService(AnotherClass anotherObject)
    {
        _anotherObject = anotherObject;
    }

    // ...
}

This is still easy and type-safe to compose with Pure DI:

IMyService service = new MyService(new AnotherClass(42));
like image 198
Mark Seemann Avatar answered Oct 16 '22 21:10

Mark Seemann


How can I pass an initial value to my service in a safe way still using IoC?

You can explicitly call a type's constructor while registering it in Unity using the IUnityContainer.RegisterInstance method:

container.RegisterInstance<IMyService>(new MyService(42));

This would give you the compile-time safety that you mention, but the cost is that it would be instantiated only once, and would be created immediately (as opposed to when it is first requested).

You could perhaps deal with this drawback by using one of the method overloads, which accepts a LifetimeManager class.

like image 1
Will Ray Avatar answered Oct 16 '22 20:10

Will Ray