98

How do I combine constructor injection with "manual" constructor parameters? ie.

public class SomeObject
{
    public SomeObject(IService service, float someValue)
    {
    }
}

Where IService should be resolved/injected by my DI container, and someValue should be specified. How do I mix the two?

George R
  • 3,784
  • 3
  • 34
  • 38
  • By manual constructor parameters do you mean when you are manually constructing the class instead of DI or you mean DI container passing in a parameter. If its the former then you could just do a constructor overload? – lahsrah Aug 04 '11 at 03:45
  • Whatever the case I always need IService, so I assume I couldn't just make an overload, sans the dependencies and it magically uses the full constructor (unless I use ServiceLocator - yuck!). – George R Aug 04 '11 at 04:22

9 Answers9

64

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();
Ulysses Alves
  • 2,297
  • 3
  • 24
  • 34
Remo Gloor
  • 32,665
  • 4
  • 68
  • 98
  • +1 Definitely avoiding is the right answer, edited my answer to agree. I know it would hide the point a bit, but as covered before elsewhere consider expressing your SomeObjectFactory with a Func ctor arg rather than using Kernel directly – Ruben Bartelink Aug 04 '11 at 15:55
  • @Ruben Bartelink: I just succested this version because I think it will be the prefered one for Ninject 2.4. The factory will autogenerated by the kernel. The Func variant will also be supported with the disadvantage that Func gives no information about the parameters and makes parameter matching more difficult. Therefore I wouldn't stick with the Func variant anymore to be able to update by simply deleting the factory implementation and change the binding. But also I would put the factory into the bootstrapper while the interface in aside of the consumer. – Remo Gloor Aug 04 '11 at 21:00
  • Thanks for explaining the subtleties of the Func injection - hadnt thought that through. I was merely making a point that in your hand-impl of the Factory [that 2.4 will autogen], you are taking a ctor dep on the Kernel which could be removed by taking a Func instead. Thinking about it now, doing so would make answer less clear and not map to what `ToFactory` is going to do. So forget what I said and thanks for explaining! And it's su_gg_est :P BTW you have mistyped the method name as Create (not CreateSomeObject). I personally will use Func in preference as I live in an obfuscated world... – Ruben Bartelink Aug 04 '11 at 23:30
  • Love the ideas though - should allow less and less container-specific mucking about outside of bootstrappers. I'm still not sure I like the default behavior of allowing IKernel to be resolvable without an explicit binding and/or a WithKernelOption() on the Bind expression, but then I dont make a lot of Factory classes so I could be wrong – Ruben Bartelink Aug 04 '11 at 23:34
  • What if the second is a runtime instance. And it will be shared through the `SomeObject` class. It makes more sense to put it in the constructor in this case. Then does it mean that `SomeObject` is a runtime type and shouldn't be in DI container in the first place? – joe Jun 19 '20 at 09:05
6

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);
    }
}
Arek
  • 1,276
  • 1
  • 10
  • 19
  • 2
    It is not safe when Load method is forgotten to be called. – Onur Demir Dec 16 '20 at 18:08
  • 1
    It's considered an OOP anti-pattern, indeed. https://medium.com/@kooliahmd/anti-pattern-chronologically-coupled-methods-eba3cbe8ef7b – Marcos R Mar 10 '21 at 11:23
  • It might be the case, but the link you provided (medium.com) is just terrible, I hope nobody learns from this. – Arek Mar 12 '21 at 08:48
6

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

Beep beep
  • 18,873
  • 12
  • 63
  • 78
4

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
 ...
AndersK
  • 35,813
  • 6
  • 60
  • 86
  • This seems like a good idea. But can others chime in if this is an anti-pattern or an acceptable pattern? – DIG Apr 10 '20 at 00:13
  • I would be grateful for any feedback. – AndersK Apr 10 '20 at 19:54
  • 1
    I'm not that experienced in .NET, but this seemed like a good idea to me. I have implemented an IEmailService that required an API key to be set in the constructor until I used DI. I have therefore created an ISecretsService which has ICollection injected into it, allowing it to access the user secrets, which is where my API key is stored. The ISecretsService is then injected into the IEmailService and could be injected into any other service that required it. – Michael Murphy May 28 '22 at 06:19
3

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.

Tomas Jansson
  • 22,767
  • 13
  • 83
  • 137
  • You seem to have a hard dependency on the container of your choice in `SomeObjectFactory`. This isn't widely recommended. Instead, inject the `IYourService` instance into the `SomeObjectFactory` constructor and let the conatiner resolve that dependency in the composition root. – Johann Gerell Aug 04 '11 at 09:08
  • Of course, my code is just an outline, it is not the final implementation. The point is that he should use a factory instead. But I updated the code according to what I think you meant. – Tomas Jansson Aug 04 '11 at 09:11
1

If 'somevalue' is always constant then you can think of using InjectionParameters while you are register your type with the container as it explained in the below post

See Here

but if that is not true, than there is no way to sepcify a parameter value while resolving a instance , you may think of moving the 'someValue' from the constructor and make it a property of the class.

Community
  • 1
  • 1
TalentTuner
  • 17,262
  • 5
  • 38
  • 63
1

In NInject, which you have tagged this with, you inject an automatically-generated Factory in the form of a Func<parameters you wish to feed in,T>, using the FuncModule as described in this post.

This approach is also available in autofac for one.

The various Factory method approaches are covered in the answers to this question.

EDIT: NB While this may be entertaining, please use @Remo Gloor's solution (and critically the advice re avoiding a solution of this nature)

Community
  • 1
  • 1
Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
0

Isn't this exactly what DI\Container::make() is for?

$object = $container->make(SomeObject::class, ['someValue' => 0.1]);
Black Mantha
  • 1,147
  • 8
  • 11
0

The approach I used was to define those parameters as abstract properties. And then create a new child class. It is similar to AndersK approach. It is not a go to solution but it suited my needs:

Instead of

public class SomeObject
{
    public SomeObject(IService service, float someValue)
    {
    }
}

You can do

public class SomeObject
{
    protected abstract Float MyFloat { get; }

    public SomeObject(IService service)
    {
        // You can use MyFloat in this class
    }
}

public class SomeUsableObject: SomeObject
{
    protected override Float MyFloat => 12.5f;

    public SomeUsableObject(IService service):base(service)
    {
    }
}