How to "properly" (idiomatic) avoid CS9107 when passing primary constructor parameters to base

2.6k views Asked by At

With C# 12, we get primary constructors for normal classes.

Consider the following code:

new Derived("XXX").Test();

public class Base(string name)
{
    protected void Log() => Console.WriteLine("Base: " + name);
}

public class Derived(string name) : Base(name)
{
    public void Test()
    {
        base.Log();
        Console.WriteLine("Derived: " + name);
    }
}

This gives the following warning for the string name parameter to the Derived primary constructor:

Program.cs(8,42): Warning CS9107 : Parameter 'string name' 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.

How can I avoid this warning, should I just squelch it using a pragma? Or is there a better way to avoid it? Is the code written the wrong way? I am not sure I can see an alternative, other than to ditch the primary constructors and rewrite the whole thing to:

new Derived("XXX").Test();

public class Base
{
    protected readonly string _name;

    protected Base(string name)
    {
        _name = name;
    }

    protected void Log() => Console.WriteLine("Base: " + _name);
}


public class Derived : Base
{
    public Derived(string name) : base(name) { }

    public void Test()
    {
        base.Log();
        Console.WriteLine("Derived: " + base._name);
    }
}

Please advise.


Note: This is a contrived example, shortened down from a more complex class hiearchy using the configuration objects from Microsoft.Extensions.Configuration, where multiple levels of classes all need access to the configuration.

3

There are 3 answers

0
Magnus On

Using a property perhaps? You probably want to keep your fields private anyway.

public class Base(string name)
{
    protected string Name => name;
    protected void Log() => Console.WriteLine("Base: " + Name);
}

public class Derived(string name) : Base(name)
{
    public void Test()
    {
        base.Log(); 
        Console.WriteLine("Derived: " + Name);
    }
}
4
shingo On

Is the code written the wrong way?

Yes, it's wrong.

string name in the primary constructor is just a parameter, but when you use it in the enclosing type, the compiler automatically turns it into a private field, so you created an additonal private field in the Derived class, and actually name in the statement Console.WriteLine("Derived: " + name); doesn't refer to the field in the base class. (In other word, if you have a method that changes name in the Base class, Derived.Test won't be changed)

To avoid the compiler automatically generating an unexpected field, you need to assign the parameter to a field or property as usual and not use this parameter in derived classes.

1
Tom McDonough On

A little off topic but on the same problem. I think it is a case the IDE (Rider) has a bug, it shouldn't allow you to remove the private field as it causes the warning in question.

public class ComponentService(
    IRepository<Component, ComponentModel> repository,
    IContext context,
    IFilterService filterService)
    : ReadOnlyComponentService(repository, context), IComponentService
{
    // The compiler is informing me this can be removed
    private readonly IContext _context = context;