I was doing some refactoring in a C# project recently and started getting the following error/warning in Visual Studio.
CS9107 Parameter 'IDateTimeProvider dateTimeProvider' is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.
It started happening as I was refactoring some code to use C# Primary Constructors and the cause of the warning was confusing to say the least.
This post will talk you through what was going on and how I fixed it.
The Original Code
Rather than have you dig through my actual code, I recreated the same problem using a simplified solution. It all starts with an abstract base class named BaseClass that has a constructor that takes an instance of IDateTimeProvider. The code isn't all that interesting except for the required IDateTimeProvider dependency on the base class constructor.
Here's the code for BaseClass.cs:
public abstract class BaseClass
{
protected IDateTimeProvider _DateTimeProvider;
public BaseClass(IDateTimeProvider dateTimeProvider)
{
if (dateTimeProvider == null)
{
throw new ArgumentNullException(nameof(dateTimeProvider), "dateTimeProvider is null.");
}
_DateTimeProvider = dateTimeProvider;
}
public abstract void DoSomething();
}
I also started with a class called OriginalStyleConstructorClass. This class has a method-style constructor like what we've had in C# since the beginning. The class extends from BaseClass and provides a method-style constructor that takes and instance of IDateTimeProvider and passes it to the constructor on the base class using the base() keyword syntax.
public class OriginalStyleConstructorClass : BaseClass
{
public OriginalStyleConstructorClass(
IDateTimeProvider dateTimeProvider) :
base(dateTimeProvider)
{
}
public override void DoSomething()
{
var currentTime =
_DateTimeProvider.GetCurrentTime();
Console.WriteLine(
$"Current Time: {currentTime}");
}
}
A key thing to note is that This class references the protected _DateTimeProvider variable from the base class. This is going to cause a little confusion as we refactor.
Refactor to use a Primary Constructor
Primary constructors are a recent addition to the C# language that were added in C# version 12. Rather than having a separate method for your constructor on a class or a struct, you can now hang that logic directly off of the class definition.
Here's the code from above that's been refactored to use a primary constructor:
public class PrimaryConstructorClassWithWarning(
IDateTimeProvider dateTimeProvider) :
BaseClass(dateTimeProvider)
{
public override void DoSomething()
{
var currentTime =
dateTimeProvider.GetCurrentTime();
Console.WriteLine(
$"Current Time: {currentTime}");
}
}
A handy little thing about using a primary constructor is that any parameter that you declare in the constructor becomes available to you as a member variable.
THIS IS IMPORTANT! Let me say it again in a different way. That dateTimeProvider arg that is part of the primary constructor -- that is basically a member variable now. And just like any member variable, it can be referenced anywhere else in the code.
In the case of the refactored code, I reference that dateTimeProvider in the DoSomething() method.
The CS9107 Warning & the Primary Constructor
Back to that warning that I was getting in Visual Studio 2022.
CS9107 Parameter 'IDateTimeProvider dateTimeProvider' is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.
I reference the dateTimeProvider member variable in the DoSomething() method...but I also pass that dateTimeProvider variable into the BaseClass() constructor.
It's that combination of using the variable and also passing the variable to the base class that's triggering the warning. The reason is that I'm essentially declaring the variable twice -- or at least referencing it in two different ways.
Reference #1 is via the variable/parameter on the constructor. Reference #2 happens after I've passed that value into the constructor on the base class and I stick that into protected IDateTimeProvider _DateTimeProvider.
It works. The code functions as expected. But I've definitely set myself up for some potential bugs because I'm sharing a reference to that value. Since that variable is a reference type (rather than a value type) I could easily modify the value in a way that's unpredictable that could mess with the logic of the base class or the child class or both.
The Fix for the Warning
Here's the fix for the warning: basically, don't reference that value two different ways. I could also turn off the warning but that wouldn't actually solve the problem -- it would just make the message go away.
Here's the modified code:
public class PrimaryConstructorClassWithoutWarning(
IDateTimeProvider dateTimeProvider) : BaseClass(dateTimeProvider)
{
public override void DoSomething()
{
var currentTime =
_DateTimeProvider.GetCurrentTime();
Console.WriteLine(
$"Current Time: {currentTime}");
}
}
The key difference in this code is that I pass dateTimeProvider to the base class constructor and then never reference that variable again. Instead, what I do in the DoSomething() method is to reference the protected variable from the base class _DateTimeProvider.
That keeps everything clear and separated and the warning message goes away.
Summary
So if you ever run into the CS9107 warning, start looking for variables created by a primary constructor that get passed to a base class that also get used in the code. It's those shared variable references that trigger the warning.
I hope this helps.
-Ben