Search code examples
c#visual-studio-2022primary-constructor

VS2022 Primary Constructor quick action not complete


Problem

When creating a class with a primary constructor, the quick actions for adding another parameter in the constructor do not work as expected. The actual assignment of the constructor parameter to the field is skipped.

How to recreate

Starting from this class:

public class MyExample(SomeDependency dependency1)
{
    readonly SomeDependency _dependency1 = dependency1;
    readonly ILogger<MyExample> _logger;
}

The _logger isn't initialized yet, but the quick actions suggest "Add parameters to MyExample(SomeDependency dependency1)...". By doing this, the class is altered to this:

public class MyExample(SomeDependency dependency1, ILogger<MyExample> logger)
{
    readonly SomeDependency _dependency1 = dependency1;
    readonly ILogger<MyExample> _logger;
}

The parameter is added but not assigned to the _logger field.

Expected behavior

Besides adding the logger parameter to the primary constructor, it should be assigned to the _logger field.

public class MyExample(SomeDependency dependency1, ILogger<MyExample> logger)
{
    readonly SomeDependency _dependency1 = dependency1;
    readonly ILogger<MyExample> _logger = logger;
}

Solution

  • Primary constructors don't support readonly fields.

    So if we take this

    public class MyExample(SomeDependency dependency1)
    {
        readonly SomeDependency _dependency1 = dependency1;
        readonly ILogger<MyExample> _logger;
        
    }
    

    If you want to initialise _logger, as it is a readonly field then you need to do it in a "full fat" constructor. You can't use a primary constructor (as it stands today)

    But you are also effectively just duplicating the number of fields in your class in the way you use it today. You don't need to redeclare them in your class.

    If SomeDependency had a property like this

    public class SomeDependency
    {
        public string MyData {get;set;}
    
    }
    

    With your current declaration, you could use this as follows

    public class MyExample(SomeDependency dependency1)
    {
        readonly SomeDependency _dependency1 = dependency1;
        //removed _logger for simplicity
    
        public void MyUsage()
        {
            var string1 = dependency1.MyData;
            var string2 = _dependency1.MyData;
        }
    }
    

    So both dependecy1 and _dependency1 both exist in your class and are the same reference. So it makes no sense.

    What you can do is "convert" the fields to properties like this;

    public class MyExample(SomeDependency dependency1)
    {
        public SomeDependency MyDependencyProperty { get; } = dependency1;
    

    By just using the getter, then it is readonly from outside the class, but of course remaining mutable from inside.

    Although I think this will still generate warnings from the compiler.