Combining DI with constructor parameters?

Solution 1:

Such constructs should be avoided whenever possible. Therefore, ask yourself: is this parameter really required as constructor argument? Or can SomeObject be replaced by a stateless one which is reused by everyone that depends on it by passing the parameter to the method you execute on the object?

e.g. Instead of

public class SomeObject
{
    private float someValue
    public SomeObject(IService service, float someValue)
    {
        this.someValue = someValue
    }

    public float Do(float x)
    {
        return this.Service.Get(this.someValue) * x;
    }
}

use

public class SomeObject
{
    public SomeObject(IService service)
    {
    }

    public float Do(float x, float someValue)
    {
        return this.Service.Get(someValue) * x;
    }
}

If it is required go for a factory:

public interface ISomeObjectFactory
{
    ISomeObject CreateSomeObject(float someValue);
}

public class SomeObjectFactory : ISomeObjectFactory
{
    private IKernel kernel;
    public SomeObjectFactory(IKernel kernel) 
    {
        this.Kernel = kernel;
    }

    public ISomeObject Create(float someValue)
    {
        return this.kernel.Get<ISomeObject>(WithConstructorArgument("someValue", someValue);
    }
}

Preview: Ninject 2.4 won't require the implementation anymore but allow

kernel.Bind<ISomeObjectFactory>().ToFactory();  // or maybe .AsFactory();

Solution 2:

You really shouldn't try to use D.I. for this. You could come up with all types of wacky solutions, but they may not make sense down the road.

Our approach is to create a factory via D.I., and the factory's Create method would then build itself out using the passed in D.I. container. We don't have to use this pattern often, but when we do it actually makes the product much cleaner (since it makes our dependency graphs smaller).

Solution 3:

Another approach - initialization in two steps (not ninject related, any DI framework):

public class SomeObject
{
    private readonly IService _service;

    public SomeObject(IService service)
    {
        // constructor only captures dependencies
        _service = service;
    }

    public SomeObject Load(float someValue)
    {
        // real initialization goes here
        // ....

        // you can make this method return no value
        // but this makes it more convienient to use
        return this;
    }
}

and usage:

public static class TestClass
{
    public static void TestMethod(IService service)
    {
        //var someObject = new SomeObject(service, 5f);
        var someObject = new SomeObject(service).Load(5f);
    }
}

Solution 4:

I would probably use a naive solution to this. If you know the value of someValue when you need it I would remove it from the constructor and add a property to your object so you can set someValue. This way you can get your object from your container and then set the value when you have the object.

My other suggestion is that you instead of accessing it directly you create a factory that you can use to create such object. Then you register the factory in your container and use the factory to create your instance. Something like this:

public class SomeObjectFactory : ISomeObjectFactory
{
    private IYourService _service;
    public SomeObjectFactory(IYourService service) 
    {
        _service = service;
    }

    public ISomeObject Create(float someValue)
    {
        return new SomeObject(_service, someValue);
    }
}

you could try a pattern like that.

UPDATE: Updated the code to reflect improvement comments.

Solution 5:

I am not sure this is a good practice, but it could be solved in a different way, If you create an interface for the parameters, then a class that implements the interface with the values that you need (or fetch from somewhere). That way DI works with those parameters as well.

interface ISomeParameters
{
  public float SomeValue { get; set; }
}

class SomeParameters : ISomeParameters
{
 public float SomeValue{ get; set; } = 42.0;
}

services.AddSingleton(ISomeParameters, SomeParameters)

public MyService(IService service, ISomeParameters someParameters)
{
  someParameters.SomeValue
 ...