Get in Touch with Your Inner Hipster Using C# 9.0 Records
Time to read: 5 minutes
If you’ve been a professional developer for more than a year or two, you’ve probably heard some hipster developer rave about functional programming. Much like how music sounds better on vinyl, programming is just better in functional languages. Your IQ jumps ten points if you can write a Fibonacci generator in Lisp, and it jumps twenty points if you can accurately describe what a monad is.
Jokes aside, like craft beer and artisan bacon, the hipsters have a point. As someone who has kids and hobbies, I can’t help you with monads, but learning about less esoteric functional programming concepts will help you write cleaner code with fewer bugs.
Functional programming concepts have been finding their way in our general-purpose programming languages for years, and C# 9 is no exception. In the latest version of C#, Microsoft has introduced some new functional goodies, mostly around immutable programming. In this post, you’re going to learn about C# records, a new feature in C# 9.0.
Review
In C#, along with most other general-purpose, object-oriented languages, class properties, and variables are mutable by default. Mutable just means that you can change properties after they are declared.
Here’s an example that should look familiar:
Mutability is acceptable in many cases, but for those pursuing a more functional style immutable data structures are the norm. For example, in F# variables are immutable by default. If you assign a value to a variable or property, you can’t change it. F# allows for mutable variables, but you have to declare them specifically.
Immutability is at the core of functional programming because the primary goal of functional programming is to represent your program as a series of pure functions. A pure function is a function with no side effects. It doesn’t mutate any state outside of itself, and it returns the same value for the same set of parameters. Pure functions are easy to test and easy to understand. Even if you aren’t using a functional programming language, you should use pure functions whenever possible.
Functional programming is also more amenable to multithreaded environments. If you are working with an immutable data structure in a pure function you can run that function in any thread without worrying that it will blast the external state in your program. In object oriented programming it’s easy to accidentally mutate shared objects, and if you’re in a multithreaded environment those mutations won’t occur in the same order every time.
Init Only setters
In C#’s quest to become more supportive of functional programming, C# 9.0 has added an init only setter. Init only setters are similar to readonly variables. They can only be set in the constructor of an object or the object’s initializer. This feature allows you to make immutable classes more easily.
Here’s an example of an init only setter in action:
To run this code, spin up a new .NET console app with one of the following:
- Visual Studio 2019 (16.8+) with the .NET 5 SDK
- Visual Studio Code (v1.15.1+) with the .NET 5 SDK and C# for Visual Studio Code (1.23.6+)
Or you can copy the code into an online IDE like .NET Fiddle, which is excellent for playing with new features and trying out code snippets. Be sure to set the Compiler field to ".NET 5".
Introducing Records
Init only setters allow you to add immutability to individual properties, but it would be nice not to type all those property declarations. Instead, let’s define everything in one line of code:
This is a C# record type. A record type is a class that implements several features by default and is declarable using the terse one-line syntax above. The goal of the record type is to make building immutable reference types a lot easier. If you’ve ever used a case class in Scala, C# records are very similar.
That one line of code creates a class with several traits:
First, the class automatically has a constructor with all of the properties included. These properties are init only.
This would be similar to a class that looks like this:
Record types also implement value equality. Equality in regular classes checks for reference equality. If you create two instances of a class with the same values, those instances are technically not equal. If you did the same with a record type, they would be equal because record types compare the values of each of the members of the class by default.
Here’s an example:
You can override the equality in a standard C# class to check for value-based equality instead, but most people don’t. If you want to know how many lines of code you’re saving, here’s a value based equality reference implementation for a truncated version of the Book
class:
Records also implement a deconstruct method by default so that you can use positional decomposition like a Tuple.
The with keyword
When dealing with immutable data types you “change” your values by generating a new object. To make this easier, records support a new operator called with
so that you can write with expressions. The with
keyword will copy your record and add or modify whatever fields you need to change.
Here’s an example:
Putting it all together
The features of record types combine well if you want to write terse, functional-flavored C#. This style shines when doing business calculations, which happens a lot in the insurance and finance industries.
Here’s an example program that generates a spread of insurance quotes. It uses record types and static methods. It’s easy to test and understand. Additionally, because everything is immutable, we can run the calculation in parallel, which saves time.
Gotchas
Like every new feature, there are some potential downsides. Record types come packed with assumptions. If one of those assumptions turns out to be invalid, you might have to rewrite your code using regular classes.
For example, if your app uses a record type and you want to use that type with a third party library, that library might not work with records. If you are leaning on some of the assumptions built into record types, like value equality or the with keyword, you’ll have to either create a class to map the values, bring in a bunch of stuff to replicate the record functionality, or edit your code to not lean on the record type assumptions. It’s not a showstopper, but it can be an annoying speed bump.
Another potential downside is that record types don’t force immutability. You can write record types with mutable properties (though you shouldn’t), leading to situations where later developers could add mutable properties and break immutability. While you can never guarantee perfection down the road, you should help people fall into the pit of success whenever possible.
Summary
One of the great things about being a .NET developer is that if you see something cool in another platform, it’s usually only a matter of time before it will show up in C#. As functional paradigms continue to grow in popularity, C# will continue to grab more functional features from other programming languages. Record types give you an ergonomic way to write immutable code in C#. They allow you to save keystrokes and write code that’s safer and easier to test.
It turns out the hipsters were right again. 🙄
Additional resources
Check out the following resources to dive deeper into the topics discussed in this post:
Microsoft F# Docs - Why Immutable?
Microsoft Docs - Value Equality Implementation
Dustin Ewers is a software developer hailing from Southern Wisconsin. He helps people build better software. Dustin has been building software for over ten years, specializing in Microsoft technologies. He is an active member of the technical community, speaking at user groups and conferences in and around Wisconsin. He writes about technology at https://www.dustinewers.com. Follow him on Twitter at @DustinJEwers.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.