Caching expensive-to-recompute properties or fields in C# records

Viewed 123

I have a C# record like the following, with a property that is expensive to compute:

sealed record MyAwesomeRecord(Dictionary<int, int> SomeDictionary)
{
    public int ExpensiveToComputeProperty => SomeDictionary.Sum(e => e.Value);

    //...
}

var original = new MyAwesomeRecord( new() { [0] = 0 });
var modified = original with { SomeDictionary = new() { [1] = 1 } };

Instead of re-computing the ExpensiveToComputeProperty value on every access, I would like to compute it only once, after "construction". But apparently with C# records, the constructor is not invoked again after modification with with. I tried the following ways to fix this:

  • Use a regular class instead, but that way I can no longer use the with syntax, which I'd like to keep using.
  • Store the ExpensiveToComputeProperty in a regular property or field. This does not work, because it is initialized once, but not after changing SomeDictionary with with.
  • AFAIK, there are plans to introduce nice syntax that would let me keep this property updated in C# 10. Unfortunately, C# 10 is not here yet.

Is there a way to use records that avoids re-doing the expensive computation?

1 Answers

Instead of using the default setter for the property, you can use a custom setter. In this setter, you can do the expensive calculation.

Here is a complete example (tested and working):

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
    sealed record MyAwesomeRecord
    {
        public MyAwesomeRecord(Dictionary<int, int> SomeDictionary)
        {
            this.SomeDictionary = SomeDictionary;
        }

        public int ExpensiveToComputeProperty { get; private set; }

        private Dictionary<int, int>? _SomeDictionary;
        public Dictionary<int, int> SomeDictionary
        {
            get => _SomeDictionary!;
            set
            {
                _SomeDictionary = value;
                ExpensiveToComputeProperty = SomeDictionary.Sum(e => e.Value);
            }
        }

        //...
    }

    class Program
    {
        static void Main()
        {
            var original = new MyAwesomeRecord(new() { [0] = 0 });

            Console.WriteLine($"ExpensiveToComputeProperty = {original.ExpensiveToComputeProperty}");

            var modified = original with { SomeDictionary = new() { [1] = 1 } };

            Console.WriteLine($"ExpensiveToComputeProperty = {modified.ExpensiveToComputeProperty}");
        }
    }
}
Related