C# 9 – immutability in application development, new record type and type omission

In late of 2020 Microsoft released .NET 5. This is Microsoft’s first step towards unifying their development platform in which they hope developers will be able to use a single set of APIs, languages and tools across different types of applications. These include web, mobile, games, IOT and cloud. 

With the release of .NET 5 also came the release of C# 9. Every new release of C# looks to simplify common coding scenarios and this release was no exception. 

In this article we will discuss immutability in the transfer of data between the back end and the client, as well as a new type introduced in C# 9 that can help us with this. 

How we structure our classes in an application 

Let’s study the architecture of a typical application. We have an API for the back end acting as a data store. We also have a front end application which can be referred to as the client. This is what the user interacts with and could be a web or mobile application. In the back end, the classes that represent the tables in the database and as a result are used to store data are known as entities. Meanwhile in the front end, we would use a series of viewmodels to display data or capture data from a user. 

Although viewmodels and entities will oftentimes share the same properties, these should NOT be shared classes. Entities should be kept lean and clean, but a viewmodel may have a plethora of validation attributes or even additional properties i.e. for dropdown lists. Furthermore, imagine we have different security levels and for each level there is a different data entry form, each for the same entity; how messy would it be if the same class was being used for each form as well as the entity?

So, we have established why viewmodels and entities should be kept separate, however tempting it may be to have them shared. We have also determined the use of each and where they belong.

We use another set of objects called DTOs, data transfer objects, in order to send data between the front and back ends. Entities are mapped to DTOs in order to send data from the back end to the front end. Viewmodels are mapped to DTOs in order to send data from the front end to the back end. This ensures all concerns are separated; each object has its own purpose and will certainly make development easier as the application grows.

Immutability in C# 9

The purpose of a DTO is to send data between the front end and the back end. If the properties of a DTO were to change, the data would no longer be accurate. Given their purpose, the properties of a DTO do not need to change, so they should be created in a way whereby they are unable to be changed; they should be immutable.

Init only setters

It is possible to create immutable objects in C# using only getters { get; }. 

Values are assigned to properties through constructors:

As we can see from the following code, this allows us to assign values to the read-only properties on construction of the object. As expected, we are unable to assign values to a property after construction of the object. 

This method however makes working with this type of object very rigid. For example, imagine our DTO has many more properties. The user would be unable to pick and choose the values they want to assign to an object, unless there was a constructor for every possible combination of values that can or cannot be assigned on construction of the object. 

One way around this would be to use an object initializer. 

However we can see that there are still a couple of complaints. The first being the object does not have a constructor that accepts 0 arguments. This can be solved by adding a simple constructor to the object. 

The second is that the properties are still read-only and are being assigned after construction of the object. 

We don’t want to add setters { set; } to the properties, remember we are trying to create an object where its values can not be changed . Using setters would make it a mutable object. Instead, we can use a new C# 9 feature that allows values to be set only on construction of the object. These are called init only setters

Here is what our revised class now looks like:

Our previous code now works a treat and we are able to assign values to ANY property without the need for that particular constructor to be in place.

Now we understand immutability, let’s explore a new type in C# 9 that can help simplify it for us. 

Records – a new type in C# 9

The record is a new type introduced in C# 9 that is immutable by design and looks to reduce the amount of boilerplate code we would typically have to write. Let’s create another DTO; we have 3 properties and a constructor that accepts all 3 as parameters:

Note the constructor that accepts all parameters and the init only setters. The same functionality of all those lines of code can be achieved in one line with the use of records.

This record type now has the exact same functionality as the previous class. Note how the syntax is very similar to that of the constructor, however here it is written in pascal case. This is because we are defining the properties. These properties are automatically created with getters and init only setters. Records also have constructors that accept all of the parameters.

The one exception to the record type is the lack of a parameterless constructor, meaning that the object initializer cannot be used. 

Cloning records

Although records are immutable by design, there is a way of changing property values after object construction. This is done via cloning. 

Although we are changing the value of a property of an existing object, it is apparent that we are assigning to a new object through the use of the with keyword. 

Records and equality

The default behaviour of C# when comparing the equality of 2 objects is to determine whether the reference to the object is the same. 

Although contact10 and contact12 share the same type AND the same values, they are not deemed the same, unlike contact10 and contact11 that have been assigned the same object. 

Equality works differently with records. Two records are equal if they share the same type AND the same values, but do not have to be the same reference. 

Person10 and person11 are deemed to be equal, although they are not referring to the same object. 

Type omission

Another new feature of C# 9 is that we can omit the type when creating a new object if the compiler can figure out what the new object is supposed to be. Take the below example; we have specified myContact2 as being of type ContactDTO so we can instantiate by simply declaring new()

The same premise applies to passing objects to parameters. 

Scroll to Top