Should I create private properties for primary constructor parameters in C# 12?

2.1k views Asked by At

I'm using C# 12. In C# 12 I can use primary constructor:

Implementation 1 :

public class Calculation(int a,int b)
{
  public int Addition() => a + b;
  public int Subtraction() => a - b;
}

Implementation 2 :

public class Calculation(int a,int b)
{
 private int _a { get; init; } = a;
 private int _b { get; init; } = b;
 public int Addition() => _a + _b;
 public int Subtraction() => _a - _b;
}

When I'm calling this method like :

console.WriteLine(new Calculation(10,25).Addition());

Both implementation working fine, so I'd like to know whether there is any advantage to using the longer Implementation 2 over the shorter Implementation 1.

2

There are 2 answers

8
Guru Stron On BEST ANSWER

There is no point in creating fields/properties yourself this way, since the compiler will create them for you in this case (decompilation @sharplab.io).

From the Tutorial: Explore primary constructors doc (they use struct but the handling of the primary ctor for classes is basically the same):

public struct Distance(double dx, double dy)
{
   public readonly double Magnitude => Math.Sqrt(dx * dx + dy * dy);
   public readonly double Direction => Math.Atan2(dy, dx);

   public void Translate(double deltaX, double deltaY)
   {
      dx += deltaX;
      dy += deltaY;
   }

   public Distance() : this(0,0) { }
}

In the previous example, the primary constructor properties are accessed in a method. Therefore the compiler creates hidden fields to represent each parameter. The following code shows approximately what the compiler generates. The actual field names are valid CIL identifiers, but not valid C# identifiers.

public struct Distance
{
   private double __unspeakable_dx;
   private double __unspeakable_dy;

   public readonly double Magnitude => Math.Sqrt(__unspeakable_dx * __unspeakable_dx + __unspeakable_dy * __unspeakable_dy);
   public readonly double Direction => Math.Atan2(__unspeakable_dy, __unspeakable_dx);

   public void Translate(double deltaX, double deltaY)
   {
       __unspeakable_dx += deltaX;
       __unspeakable_dy += deltaY;
   }

   public Distance(double dx, double dy)
   {
       __unspeakable_dx = dx;
       __unspeakable_dy = dy;
   }
   public Distance() : this(0, 0) { }
}

The potential downside is that the generated field is mutable:

public class Calculation(int a, int b)
{
    public void Mutate() => a = 100;
    // ...
}

Though your approach does not fix that but introduces some clutter when you can confuse _a and a. There is no way to mark the generated field as readonly for now (but there are plans for the feature - see this LDM notes doc and this proposal) but you can shadow the ctor parameter by using field with the same name (or property):

public class Calculation2(int a, int b)
{
    private readonly int a = a;    
    private readonly int b = b;
    // public void Mutate() => a = 100; // does not compile
    public int Addition() => a + b;
    public int Subtraction() => a - b;
}

See also:

8
Mike Nakis On

Advantages of the 2nd implementation:

  1. It consists of more lines of code. (Useful if you are getting paid by the line of code written.)
  2. It is more difficult to read. (Direct result of being longer.)
  3. It introduces some entirely unnecessary private properties, which replace the fields that the compiler would otherwise create to store the constructor parameters, thus making it run slightly slower on debug runs, which tend to be unoptimized.
  4. It opens up the possibility that in different places of your code you will inadvertently access both the constructor parameter and the private property, thus causing that parameter to occupy double storage.
  5. It gives python programmers a warm fuzzy feeling by shoe-horning into an otherwise fine coding style the "prefix privates with underscore" convention which might be necessary in python but is entirely unwarranted in C#.
  6. It largely defeats the very purpose of having a default constructor in the first place.