Take advantage of record types in C# 9 to build immutable types and thread-safe objects. Credit: Thinkstock Immutability makes your objects thread-safe and helps improve memory management. It also makes your code more readable and easier to maintain. An immutable object is defined as an object that, once created, can’t change. Thus an immutable object is inherently thread-safe and immune from race-conditions. Until recently, C# did not support immutability out-of-the-box. C# 9 introduces support for immutability with new init-only properties and record types. Init-only properties can be used to make the individual properties of an object immutable, and records can be used to make the entire object immutable. Because immutable objects don’t change their state, immutability is a desirable feature in many use cases such as multi-threading and Data Transfer Objects. This article discusses how we can use init-only properties and record types in C# 9. To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here. Create a console application project in Visual Studio First off, let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2019 is installed in your system, follow the steps outlined below to create a new .NET Core console application project in Visual Studio. Launch the Visual Studio IDE. Click on “Create new project.” In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed. Click Next. In the “Configure your new project” window shown next, specify the name and location for the new project. Click Create. Following these steps will create a new .NET Core console application project in Visual Studio 2019. We’ll use this project in the subsequent sections of this article. Use init-only properties in C# 9 Init-only properties are those that can be assigned values only at the time when the object is initialized. Refer to the following class that contains init-only properties. public class DbMetadata { public string DbName { get; init; } public string DbType { get; init; } } You can use the following code snippet to create an instance of the DbMetadata class and initialize its properties. DbMetadata dbMetadata = new DbMetadata() { DbName = "Test", DbType = "Oracle" }; Note that subsequent assignments to the init-only fields are illegal. Hence the following statement will not compile. dbMetadata.DbType = "SQL Server"; Use record types in C# 9 A record type in C# 9 is a lightweight, immutable data type (or a lightweight class) that has read-only properties only. Because a record type is immutable, it is thread-safe and cannot mutate or change after it has been created. You can initialize a record type only inside a constructor. You can declare a record using the record keyword as shown in the code snippet below. public record Person { public string FirstName { get; set; } public string LastName { get; set; } public string Address { get; set; } public string City { get; set; } public string Country { get; set; } } Note that merely marking a type as a record (as shown in the preceding code snippet) will not give you immutability on its own. To give immutability to your record type you must use init properties as shown in the code snippet below. public record Person { public string FirstName { get; init; } public string LastName { get; init; } public string Address { get; init; } public string City { get; init; } public string Country { get; init; } } You can use the following code snippet to create an instance of the Person class and initialize its properties. var person = new Person { FirstName = "Joydip", LastName = "Kanjilal", Address = "192/79 Stafford Hills", City = "Hyderabad", Country = "India" }; Use with-expressions in C# 9 You might often want to create an object from another object, if some of the properties have identical values. However, the init-only properties of a record type will prevent this. For example, the following code snippet will not compile because all of the properties of the record type named Person are init-only by default. var newPerson = person; newPerson.Address = "112 Stafford Hills"; newPerson.City = "Bangalore"; Fortunately there is a workaround—the with keyword. You can take advantage of the with keyword to create an instance of a record type from another by specifying the changes in the values of the properties. The following code snippet illustrates how this can be accomplished. var newPerson = person with { Address = "112 Stafford Hills", City = "Bangalore" }; Inheritance in record types in C# 9 Record types support inheritance. That is, you can create a new record type from an existing record type and add new properties. The following code snippet illustrates how you can create a new record type by extending an existing one. public record Employee : Person { public int Id { get; init; } public double Salary { get; init; } } Positional records in C# 9 Instances of record types that are created using positional arguments are immutable by default. In other words, you can create an immutable instance of a record type by passing an ordered list of parameters using constructor arguments as shown in the code snippet given below. var person = new Person("Joydip", "Kanjilal", "192/79 Stafford Hills", "Hyderabad", "India"); Check record instances for equality in C# 9 When you check for equality of two instances of a class in C#, the comparison is based on the references of those objects (identity). However, if you check for equality of two instances of a record type, the comparison is based on the values within the instances of the record type. The following code snippet illustrates a record type named DbMetadata that consists of two string properties. public record DbMetadata { public string DbName { get; init; } public string DbType { get; init; } } The following code snippet shows how you can create two instances of the DbMetadata record type. DbMetadata dbMetadata1 = new DbMetadata() { DbName = "Test", DbType = "Oracle" }; DbMetadata dbMetadata2 = new DbMetadata() { DbName = "Test", DbType = "SQL Server" }; You can check for equality using the Equals method. The following two statements will display “false” at the console window. Console.WriteLine(dbMetadata1.Equals(dbMetadata2)); Console.WriteLine(dbMetadata2.Equals(dbMetadata1)); Consider the following code snippet that creates a third instance of the DbMetadata record type. Note that the instances dbMetadata1 and dbMetadata3 contain identical values. DbMetadata dbMetadata3 = new DbMetadata() { DbName = "Test", DbType = "Oracle" }; The following two statements will display “true” at the console window. Console.WriteLine(dbMetadata1.Equals(dbMetadata3)); Console.WriteLine(dbMetadata3.Equals(dbMetadata1)); Although a record type is a reference type, C# 9 provides synthetic methods to follow value-based equality semantics. The compiler generates the following methods for your record type to enforce value-based semantics: An override of Object.Equals(Object) method A virtual Equals method that accepts a record type as its parameter An override of the Object.GetHashCode() method Methods for the two equality operators, namely operator == and operator != Record types implement System.IEquatable Additionally, record types provide an override of the Object.ToString() method. These methods are generated implicitly and you need not re-implement them. Check for Equals method in C# You can check to see if the Equals method has been generated implicitly. To do this, add an Equals method in the DbMetadata record as shown below. public record DbMetadata { public string DbName { get; init; } public string DbType { get; init; } public override bool Equals(object obj) => obj is DbMetadata dbMetadata && Equals(dbMetadata); } When you compile your code, the compiler will flag an error with the following message: Type ‘DbMetadata’ already defines a member called ‘Equals’ with the same parameter types Although a record type is a class, the record keyword provides additional value-like behaviors and semantics that make a record different from a class. A record is itself a reference type but it uses its own built-in equality check — the equality is checked by value and not reference. Finally, note that records can be mutable but they are primarily designed for immutability. How to do more in C#: How to use implicit and explicit operators in C# Singleton vs. static classes in C# How to log data to the Windows Event Log in C# How to use ArrayPool and MemoryPool in C# How to use the Buffer class in C# How to use HashSet in C# How to use named and optional parameters in C# How to benchmark C# code using BenchmarkDotNet How to use fluent interfaces and method chaining in C# How to unit test static methods in C# How to refactor God objects in C# How to use ValueTask in C# How to use immutability in C How to use const, readonly, and static in C# How to use data annotations in C# How to work with GUIDs in C# 8 When to use an abstract class vs. interface in C# How to work with AutoMapper in C# How to use lambda expressions in C# How to work with Action, Func, and Predicate delegates in C# How to work with delegates in C# How to implement a simple logger in C# How to work with attributes in C# How to work with log4net in C# How to implement the repository design pattern in C# How to work with reflection in C# How to work with filesystemwatcher in C# How to perform lazy initialization in C# How to work with MSMQ in C# How to work with extension methods in C# How to us lambda expressions in C# When to use the volatile keyword in C# How to use the yield keyword in C# How to implement polymorphism in C# How to build your own task scheduler in C# How to work with RabbitMQ in C# How to work with a tuple in C# Exploring virtual and abstract methods in C# How to use the Dapper ORM in C# How to use the flyweight design pattern in C# Related content feature What is Rust? Safe, fast, and easy software development Unlike most programming languages, Rust doesn't make you choose between speed, safety, and ease of use. Find out how Rust delivers better code with fewer compromises, and a few downsides to consider before learning Rust. By Serdar Yegulalp Nov 20, 2024 11 mins Rust Programming Languages Software Development how-to Kotlin for Java developers: Classes and coroutines Kotlin was designed to bring more flexibility and flow to programming in the JVM. Here's an in-depth look at how Kotlin makes working with classes and objects easier and introduces coroutines to modernize concurrency. By Matthew Tyson Nov 20, 2024 9 mins Java Kotlin Programming Languages analysis Azure AI Foundry tools for changes in AI applications Microsoft’s launch of Azure AI Foundry at Ignite 2024 signals a welcome shift from chatbots to agents and to using AI for business process automation. By Simon Bisson Nov 20, 2024 7 mins Microsoft Azure Generative AI Development Tools news Microsoft unveils imaging APIs for Windows Copilot Runtime Generative AI-backed APIs will allow developers to build image super resolution, image segmentation, object erase, and OCR capabilities into Windows applications. By Paul Krill Nov 19, 2024 2 mins Generative AI APIs Development Libraries and Frameworks Resources Videos