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?
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();
}
}
Looks like this issue might be related to the IDE. The issue is noticed in JetBrains Rider but not VS2022.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With