In my job we are developing ASP.NET core applications. I keep seeing the follwing pattern used with dependency-injection. In this example Logic
depends on OtherLogic
:
public class Logic {
private readonly OtherLogic myOtherLogic;
public Logic(OtherLogic myOtherLogic) {
// I mean specifically this pattern of checking injected dependencies for null:
this.myOtherLogic = myOtherLogic
?? throw new ArgumentNullException(nameof(myOtherLogic));
}
}
All dependencies are added to an IServiceCollection
during service configuration and both (Logic
and OtherLogic
) are only retrieved from dependency injection. In my current project I’m using DotNet 5 and I have enabled nullable reference types if that matters.
I’d like to drop this Guard Clause pattern, because I believe the injector will always throw an exception (during service configuration) when it’s unable to resolve a non-optional service.
My question is, in which circumstances the injector could inject a null-reference for a non-optional service? Is it even possible?
This article doesn’t mention the possibility of null being injected, but as far as I can see, also doesn’t explicitly deny it. Just want to make sure I don’t run into trouble later on, thanks for your time 🙂
>Solution :
Under ‘normal’ conditions (i.e. when solely depending on the use of Auto-Wiring) null
values can’t be injected. The following code snippets, however, show examples of when null
references can be injected into Logic
:
Case 1: Manual construction
new Logic(null);
Case 2: Manual construction using factory registration
services.AddTransient(c => new Logic(null));
Case 3: Direct null injection through factory registration
services.AddTransient(c => new Logic(null));
Case 4: Incorrect use of GetService
opposed to GetRequiredService
in factory
services.AddTransient(c => new Logic(c.GetService<OtherLogic>()));
Case 5: Auto-registration with dependency that returns null
from its factory registration
services.AddTransient<Logic>();
services.AddTransient<OtherLogic>(c => null);
In my opinion, option 5 should not be allowed, and I consider it a design flow in MS.DI to allow a factory registration to return null
. But that still leaves the other three cases where the Logic
constructor is called using plain-old C#.
Whether or not you want to be very safe and protect the class’s pre conditions using a Guard Clause is up to you. Personally, for applications where I make use of a DI Container that doesn’t allow case 5 and makes it hard to do case 4, I typically leave out the Guard Clauses. This even allows me to use the more concise record
syntax:
public sealed record Logic(OtherLogic MyOtherLogic)
{
}