Combining DI with constructor parameters?
Asked Answered
L

9

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?

Leroylerwick answered 4/8, 2011 at 3:29 Comment(2)
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?Fordone
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!).Leroylerwick
E
66

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();
Endways answered 4/8, 2011 at 11:0 Comment(5)
+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<T> ctor arg rather than using Kernel directlyMarnimarnia
@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.Endways
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...Marnimarnia
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 wrongMarnimarnia
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?Doering
B
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).

Bombe answered 4/8, 2011 at 5:45 Comment(0)
R
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);
    }
}
Rh answered 15/3, 2018 at 14:10 Comment(3)
It is not safe when Load method is forgotten to be called.Bluhm
It's considered an OOP anti-pattern, indeed. medium.com/@kooliahmd/…Ogren
It might be the case, but the link you provided (medium.com) is just terrible, I hope nobody learns from this.Rh
B
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
 ...
Bashful answered 27/2, 2020 at 4:46 Comment(3)
This seems like a good idea. But can others chime in if this is an anti-pattern or an acceptable pattern?Hb
I would be grateful for any feedback.Bashful
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.Autoerotism
L
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.

Luca answered 4/8, 2011 at 5:41 Comment(2)
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.Clarenceclarenceux
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.Luca
T
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.

Trichroism answered 4/8, 2011 at 5:37 Comment(0)
M
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)

Marnimarnia answered 4/8, 2011 at 6:13 Comment(0)
F
0

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

$object = $container->make(SomeObject::class, ['someValue' => 0.1]);
Fingering answered 10/1, 2023 at 10:20 Comment(0)
M
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)
    {
    }
}
Moreira answered 9/5, 2023 at 15:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.