The Joy of Read-Only Getters

Read-Only getters (read-only properties in C#) are an excellent option for managing volatile object state that should always be up to date, like a calculated value or formatted string.

In a previous write-up on whether we ought to favour object construction or object initialisation, we produced a constructor for an InvoiceLine class. Today we’ll improve on our work. 

 

Again, here is the constructor for InvoiceLine

  public InvoiceLine(Product product, decimal quantity)
  {
     Product = product;
     Quantity = quantity;
     UnitPrice = product.UnitPrice;
     Subtotal = UnitPrice * Quantity;
  }

 

Say, we had the following unit test:

  [Fact]
  public void When_Construct_InvoiceLine_Then_Calculates_Subtotal()
  {
     var apple = new Product("Apple", unitPrice: 2);
     var line = new InvoiceLine(apple, quantity: 3);
     line.Subtotal.Should().Be(6);   // = 2 * 3
  }

 

Would the test pass? I reckon it would. Subtotal is the product of Quantity (here 2), and UnitPrice (here 3). 3 times 2 equals 6

What if we had another unit test? One where we change the Quantity. Would that unit test pass? Here’s a representative test:

  [Fact]
  public void When_Change_Quantity_Then_Recalculates_Subtotal()
  {
     var apple = new Product("Apple", unitPrice: 2);
     var line = new InvoiceLine(apple, quantity: 3);
     line.Quantity = 5;
     line.Subtotal.Should().Be(10); // = 2 * 5
  }

 

Unfortunately, this new unit test will fail. The error message will be something like ‘Expected Subtotal to be 10, but was actually 6‘.

Hmm, the Subtotal amount after construction was also 6. It looks like it hasn’t updated.

And fair enough too. We only assign the Subtotal in the constructor. The value cannot change for an InvoiceLine object after that time.  

OK, but how do we fix this? How do we get this unit test to pass?

 

Here is one way:

  public decimal Subtotal { get { return Quantity * UnitPrice; } };

and rewritten to be more modern and concise,

  public decimal Subtotal => Quantity * UnitPrice;

 

Alright, we have changed Subtotal into a calculated read-only property.

But now the Subtotal assignment in the constructor will give us a compilation error. We must remove it:

  public InvoiceLine(Product product, decimal quantity)
  {
     Product = product;
     Quantity = quantity;
     UnitPrice = product.UnitPrice;
  }

 

So much neater. In my opinion, the constructor was starting to look a bit bloated with four separate assignment lines.

The advantages of moving the Subtotal into a separate read-only property are:

  • It’s more up-to-date: The Subtotal recalculates just-in-time using the values in Quantity and UnitPrice. In comparison, assigning the Subtotal in the constructor will not alter the value when Quantity, Unit Price or both change.
  • It’s Tidier: Having the Subtotal as its own property, rather than bloating the constructor with another assignment line, is so much neater.
  • It’s Safer – Using the lambda arrow notation, the Subtotal property is guaranteed to be read-only – we cannot assign a value to it. In the case of the constructor assignment, it’s too easy to inadvertently leave the Subtotal property declaration open with a public setter. 

When needing to expose up-to-date calculated values, consider read-only getters

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply