What are C# records, exactly?

Let’s discover why C# records are a great easy-to-use feature to pack together some data!

This article is also available on Medium.

After doing a bit of C#, you’ve probably dealt with classes and structs multiple times. Those are very common methods for creating custom types and they make it easy to define your own data.

Starting with C# 9, there is, however, another less-known way to define your own types: records. They are an easy-to-use built-in tool to encapsulate data and create a reference type with a concise syntax.

So, today, let’s have a quick look at this C# feature and see what records are, why they’re useful, and how to use them!

Creating some simple records

Ok, first of all, we’ll start by having a peek at the simplest form of record. Basically, all we have to do is use the record keyword and a few positional parameters to define its various fields:

Then, all we have to do is print the object and the program will have an auto-generated formatted display for it:

As you can see, records are pretty cool in terms of readibility: we can quickly define some bundle of data with named fields, and we can easily initialise any instance.

By default, the fields are all public – they can be accessed from anywhere outside the record or its instances. To restrict the access and better encapsulate your data, it can be interesting to “re-define” the field by creating a property with the same name inside of the record.

Typically, to hide an ID field, you can create an internal field to replace it (you can see in the output that the CleanClient record doesn’t display its ID):

If you need some additional data in your record but want it to be initialised automatically and not via the constructor, then you can have some other fields in your record definition with an initial value. If you don’t give them any access modifier, they will be private – but you can use the public / protected / internal as usual:

And if you want to create a copy of a record quickly, you can use the with keyword. In a nutshell, the idea is to pass the fields to override with their new values to create a new record instance based on the source object:

Two interesting properties of records

Whenever you work with records, you should keep in mind the following things. By default (meaning with positional parameters as shown before):

  • they work with value equality
  • they create immutable objects

What does this mean?

First, value equality is the idea that when you compare two record instances, you check their content instead of their actual memory addresses. Typically, for records, they will be considered the same as soon as all their fields are equal. This is different from the reference equality used by classes for example, where two objects are only equal if they point to the same address in memory.

You can check this out very easily by creating two records with the same values and using the ReferenceEquals() built-in function:

The second point is about immutability. In short, an immutable type cannot be modified once it’s been instantiated. So, for example, you can’t prepare a record, and then change one of its field’s value – if you try, you’ll get this kind of error:

If you keep this basic definition of the record type, to “change” the value of a record instance, you’ll just have to re-create one, for example using the with keyword we saw before if you want to keep some of the data as-is.

On the other hand, and although it’s not the most typical use case for records, if you want to make your records mutable, then you can actually change your record type to give your fields getters and setters, like this:

In any case, when working with records, remember that, by default, equality doesn’t work the same as with classes, and that you can’t update their contents after creating them!

Playing around with record instances

Using a record struct

If you use the record keyword on its own, you truly get a record class reference type. Sometimes, you might want to make a value type and therefore get a struct-equivalent instead. In C# 10 and onward, to do this, we just have to create a record struct:

Structs are usually a good choice when you want your custom type to be just about data, and immutable or rarely boxed/mutated values. Record structs are similar tools with a nice and concise syntax 🙂

They can help “protect” your data since passing it to a function will create a copy and leave the initial object intact:

Inheriting from another record

If you want to share some data or behaviour easily, records can use inheritance just like classes. However, a record can only inherit from a record, not from a class (and, conversely, a class cannot inherit from a record).

So, for example, we can do the following to quickly get some custom data types:

As you can see, the inheritance works as usual with the abstract, virtual and override keywords, and derived constructors for the child classes.

However don’t forget that for records, equality is checked both with the values of the fields and the (exact) type of the record instance… so if you try to compare two records with exactly the same data, but that use two different derived classes, they won’t be equal:

Why use records?

At this point, we know a few things about records that help us better understand when they can be useful.

Basically, records are great whenever we want to encapsulate data in an immutable type using a simple and readable syntax. You should use them when you need to create a custom value-like state.

Typically, they are pretty useful for the return values of REST APIs because they can easily be translated into JSON responses; or when you have some search parameters to analyse and use in your logic.

This type of in-between data structures are often called “data transfer objects” (aka DTO). They are at the heart of any data exchange process such as a search in a database, a call to an API or even a re-formatting of an object into a better-suited interface for another layer of your application.

Conclusion

And here you are: this was just a quick peek at records, how to use them and when they can be an interesting tools for your C# projects! Of course, there’s still a lot to discover and to say…

… so, what do you think: did I forget your favourite tip about C# records? Feel free to tell us in the comments 😉

As usual, thanks a lot for reading, and see you soon for more posts on coding and tech!

Leave a Reply

Your email address will not be published.