Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a primary constructor with dependency injection?

Tags:

c#

.net

I have been reading the introductory guide for primary constructors in c#12 and I'm getting some unexpected results (https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/primary-constructors).

I have some code like:

public class ServiceA(IServiceB service)
{
    public async Task<bool> Method() {
        return await service.GetValue();
    }
}

Now, if I create a unit test like this:

[Fact]
public void Test1() {
    var mockB = Substitute.For<IServiceB>();
    _sut = new SeviceA(mockB);
    _sut.Method();
    Assert.NotNull(mockB);
}

In the call to _sut.Method();, inside, the service that is provided in the test via a mock, it's actually null in ServiceA.Method. The mock is correctly created in the unit test, but it doesn't make it to the method.

A way I found to solve this is by creating a property or field to capture the Primary constructor parameter and then use that instead of the parameter. Something like this:

public class ServiceA(IServiceB service)
{
    private IServiceB ServiceProperty { get; } = service; //capture the parameter
    public async Task<bool> Method() {
        return await ServiceProperty.GetValue(); //use property instead of parameter
    }
}

As I said, the above now works, however from what I understand from the docs, that parameter capturing should not be necessary: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/primary-constructors#dependency-injection

Another common use for primary constructors is to specify parameters for dependency injection. The following code creates a simple controller that requires a service interface for its use: C#

 public interface IService
 {
     Distance GetDistance();
 }
 public class ExampleController(IService service) : ControllerBase
 {
     [HttpGet]
     public ActionResult<Distance> Get()
     {
         return service.GetDistance();
     }
}

The primary constructor clearly indicates the parameters needed in the class. You use the primary constructor parameters as you would any other variable in the class.

-> Am I misunderstanding Primary constructors?

like image 947
Gonan Avatar asked Dec 18 '25 21:12

Gonan


2 Answers

You shouldn't really need to do this but you could try using a read-only property that returns the service passed in through the primary constructor.

public class ServiceA(IServiceB service)
{
    private IServiceB Service => service;
    
    public async Task<bool> Method() 
    {
        return await Service.GetValue();
    }
}

Update:

Looks like this issue might be related to the IDE. The issue is noticed in JetBrains Rider but not VS2022.

Update 2:

This issue is fixed in Rider version 2024.3 EAP 6 (build date Jan 15 2024) as mentioned in the comment by @bas. Note: as stated it may be fixed in an earlier version but this has not yet been confirmed.

like image 122
YungDeiza Avatar answered Dec 21 '25 14:12

YungDeiza


I'm not sure if this will solve it for everyone but it solved it for me.

The problem was actually that I needed more Primary Constructors.

Some of the injected services didn't have Primary Constructors so I tested adding the constructors to my service implementations and after that it started working as expected.

I still think it should work without that but now it works even without capturing the parameters.

Hopefully this helps someone!

Edit: Just and FYI the parameters still show null in the debugger, but they work at runtime for some reason.

like image 25
Emilio Urriola Avatar answered Dec 21 '25 13:12

Emilio Urriola



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!