Robert Nish
Robert Nish

Reputation: 81

.NET Core fundamentals - Constructor injection behavior

I am new to .NET core and I want to start a project using it. I have been reading official documentation and tutorials.

In the Microsoft official documentation about dependency injection, in the section of "Constructor injection behavior", it has been explained that "When services are resolved by ActivatorUtilities, constructor injection requires that only one applicable constructor exists. Constructor overloads are supported, but only one overload can exist whose arguments can all be fulfilled by dependency injection."

In that statement what they mean by "only one applicable constructor exists"? Could anyone please explain. If there are only one, how it supports constructor overloading. It is bit confusing to me.

PS:- Feel free to down-vote, but please be kind enough to mention where should I ask this question, in the comments section if this question doesn't match the criteria.

Upvotes: 0

Views: 656

Answers (1)

Dai
Dai

Reputation: 155135

It means that when you have a class that uses DI, it must have exactly one constructor where all parameters are either registered dependencies (or services) or have default values.

I note that the documentation doesn't say what happens if it does but I assume it will throw an exception or fail somehow (e.g. the factory method returning null?)

The documentation also states:

Constructors can accept arguments that aren't provided by dependency injection, but the arguments must assign default values.

By example - if we have a DI context where these services are available:

  • IImageResizerService
  • IImageSavingService
  • IImageObjectRecognizerService

This is okay (single satisfiable constructor):

public class DefaultImageProcessingService : IImageProcessingService
{
    public DefaultImageProcessingService
    (
        IImageResizerService          resizer,
        IImageSavingService           saver,
        IImageObjectRecognizerService recognizer,
        String defaultFileName = null,
        Int32  maxSaveAttempts = 3
    )
    {
        this.resizer    = resizer ?? throw new ArgumentNullException( nameof(resizer) );
        this.saver      = saver   ?? throw new ArgumentNullException( nameof(saver) );
        this.recognizer = recognizer?? throw new ArgumentNullException( nameof(recognizer) );
    }
}
  • It has only 1 constructor
  • All constructor arguments are registered services or have default values.

But this is not okay (multiple satisfiable constructors):

public class DefaultImageProcessingService : IImageProcessingService
{
    public DefaultImageProcessingService
    (
        IImageResizerService resizer,
        String defaultFileName = null,
        Int32 maxSaveAttempts = 3
    )
    {
        this.resizer    = resizer ?? throw new ArgumentNullException( nameof(resizer) );
    }

    public DefaultImageProcessingService
    (
        IImageSavingService saver,
        IImageObjectRecognizerService recognizer,
        String defaultFileName = null,
        Int32 maxSaveAttempts = 3
    )
    {
        this.saver      = saver   ?? throw new ArgumentNullException( nameof(saver) );
        this.recognizer = recognizer?? throw new ArgumentNullException( nameof(recognizer) );
    }
}

It isn't acceptable because it has two constructors that both have registered services as parameters. So the DI factory doesn't know which constructor to use because it could use either of them.

This example isn't acceptable either (single unsatisfiable constructor):

public class DefaultImageProcessingService : IImageProcessingService
{
    public DefaultImageProcessingService
    (
        IImageResizerService resizer,
        IImageSavingService saver,
        IImageObjectRecognizerService recognizer,
        IMysteryService mystery,
        String defaultFileName = null,
        Int32 maxSaveAttempts = 3
    )
    {
        this.resizer    = resizer ?? throw new ArgumentNullException( nameof(resizer) );
        this.saver      = saver   ?? throw new ArgumentNullException( nameof(saver) );
        this.recognizer = recognizer ?? throw new ArgumentNullException( nameof(recognizer) );
        this.mystery = mystery ?? throw new ArgumentNullException( nameof(mystery) );
    }
}

It isn't acceptable because IMysteryService is not registered.

This is okay (multiple constructors, but exactly 1 is satisfiable):

public class DefaultImageProcessingService : IImageProcessingService
{
    public DefaultImageProcessingService
    (
        IImageResizerService resizer,
        IImageSavingService saver,
        IImageObjectRecognizerService recognizer,
        String defaultFileName = null,
        Int32 maxSaveAttempts = 3
    )
    {
        this.resizer    = resizer ?? throw new ArgumentNullException( nameof(resizer) );
        this.saver      = saver   ?? throw new ArgumentNullException( nameof(saver) );
        this.recognizer = recognizer?? throw new ArgumentNullException( nameof(recognizer) );
    }

    public DefaultImageProcessingService
    (
        IImageResizerService resizer,
        IImageSavingService saver,
        IImageObjectRecognizerService recognizer,
        IMysteryService mystery,
        String defaultFileName = null,
        Int32 maxSaveAttempts = 3
    )
    {
        this.resizer    = resizer ?? throw new ArgumentNullException( nameof(resizer) );
        this.saver      = saver   ?? throw new ArgumentNullException( nameof(saver) );
        this.recognizer = recognizer ?? throw new ArgumentNullException( nameof(recognizer) );
        this.mystery = mystery ?? throw new ArgumentNullException( nameof(mystery) );
    }
}

It's acceptable because one of the constructors is only uses registered services and default values (the first constructor). The second constructor will not be used by the DI factory because it has a non-satisfiable parameter (IMysteryService).

Upvotes: 1

Related Questions