C# 9.0 Immutable Records – Yes!

Mads Torgersen, C# Lead Designer recently posted about development for upcoming C# 9.0. There’s a whole bunch of features that made me say Yes, Do It, When Can I Get It, Just Yes!

But first a story to explain why I’m so enthusiastic.

F#

What? I thought this was about C#! Patience, grasshopper…

Project

Last year I was lucky enough to build a small system in F#. I work for a large company with development mostly in C# and TypeScript. We’re in the middle of a major shift into the cloud so there’s a lot of learning going on for software engineering practices. One theme in up-skilling developers was to introduce functional programming techniques. Many of us followed the Programming Languages course by Dan Grossman to get a broad understanding of different programming paradigms. This project was a further step along this path, an experiment to see if a team could learn enough F# quickly to build production code we could support. Scott Wlaschin, a well known voice in the F# community, was brought in to do initial training and help us move as quickly as possible.

F# is great. Anything you could build in C# you can build in F#, and it’s just .NET Core so will deploy anywhere. F#’s ML heritage gives a bunch of advantages. Some of our junior developers reported they felt able to learn and build in F# just as quickly as in C#. But for many there was a lot to unlearn to be able to use F# well. So the system is in production and rolled out to support several thousand users, but the training effort currently makes it unlikely we’ll use F# for many future projects.

One thing I learned

Just the experience of learning and use another programming languages improves your code in every language. Learning a functional language will change your C# style, your Javascript style, for the better.

One of the things I really liked about F# in a functional style was the separation of data structures and functions. This leans nicely towards my current coding style, and is useful for much of the data processing my company does. It also helps avoid concurrency issues in modern systems.

Switching between F# and C# the biggest thing I missed was simple immutable data types with value-based equality. I could build C# classes to be immutable, and override Equals() and GetHashCode() to implement value-based equality semantics. But it took time and effort, so was hard to get my whole team to stick to it. And it was fragile, every new property required the constructor, the Equals(), the GetHashCode() to be updated, with no way to check except in manual code reviews.

I wished there was a [MakeMeImmutable] attribute I could add to a C# class to give it the automated behaviour of F# classes. You can show the equivalent C# from the F# compiler, it’s literally automatically building a correct, performant override for Equals() and GetHashCode() for you. If only C# had a switch to do that too! Well soon it will!

C# 9

C#, and other widely used programming languages, have been borrowing features from functional programming languages for a while now.

C# already has:

  • local var
  • lambda expressions
  • generics
  • nullable reference types
  • tuples
  • pattern-matching
  • async await

These were already in other languages like ML, Haskell, Lisp. Just waiting for us to realise we needed them.

For C# 9 Mads mentioned Records and With-expressions and Value-based equality among the long list of features in progress.

This is literally immutable data types from F#, but in C#.

Mark a class as a “record” or a “data class” and you get a shorter definition syntax, a simple way to copy objects while making small changes, and correct Equals() and GetHashCode() for free.

And it all works nicely if you have need to subclass them. Yes, correct Equals() and GetHashCode() behaviour for value-based subclasses.

A record is just a C# class. So you can add extra methods, you can add mutable data. And this might be useful sometimes. But you lose the expressiveness of “this is just data, it’s safe, it can’t do anything unexpected”.

C# 8 equivalent immutable class is pretty long so hardly anyone bothers.

public class Person
{
  string FirstName { get; }
  string LastName { get; }

  public Person(string firstName, string lastName)
  {
    FirstName = firstName;
    LastName = lastName;
  }

  public Person WithLastName(string newLastName)
  {
    return new Person(FirstName, newLastName);
  }

  public override bool Equals(object obj)
  {
    var person = obj as Person;
    return person != null &&
      FirstName == person.FirstName &&
      LastName == person.LastName;
  }

  public override int GetHashCode()
  {
    var hashCode = 1938039292;
    hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(FirstName); 
    hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(LastName);
    return hashCode;
  }
}

var currentPerson = new Person(firstName: "Scott", lastName: "Hunter");
var updatedPerson = currentPerson.WithLastName("Hanselman");

New C# 9 is clean and short and descriptive.

public data class Person { string FirstName; string LastName; }

var currentPerson = new Person { FirstName = "Scott", LastName = "Hunter" };
var updatedPerson = currentPerson with { LastName = "Hanselman" };

I’m going to be very happy when this is ready. It’ll make it easy to follow my preferred coding style, and easier for my team to use it. More of our code will be side-effect free and safe if parallelized, while being shorter and easier to write and to read.

So thank you Mads and everyone else involved.

Links

No Comments

Leave a Reply

Your email address will not be published. Required fields are marked *