C# 9 Record Types: Immutable Value Types, Syntax, & Usage

Introduction

A big improvement in C# 9 was the addition of record types. Record types can be used by developers to create immutable value types. The record keyword is used to define a record type. Record kinds are defined by the record keyword. A record type is an immutable value type that, once created, cannot be altered.

C# 9 offers a method for creating immutable data types called records. Like enums, structs, and classes, a record type is defined using the record keyword. Record types, in contrast to these other construct types, are intended to be immutable by default. The compiler guarantees that properties cannot be altered after they are generated when you define a record type with methods and properties.

Its main characteristics are as follows.

  • To create a reference type with immutable properties, use positional syntax.
  • Integrated formatting for the screen.
  • equality based on values.
  • succinct syntax combined with non-destructive mutation.
  • backing for tiers of inheritance.

With the exception of the declaration keyword, a record type's declaration syntax is quite identical to a class's.

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

public record Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

The aforementioned example demonstrates how to declare an Employee record by nominal construction using conventional getters and setters.

Mutable Property

Although records are not always immutable, their primary use is with immutable data models.

We used the set accessor to declare the record properties in the previous example. This indicates that an item is mutable if its state can be changed after it is created. However, record types that are declared with positional parameters are by default immutable.

public record Employee(int Id, string Name, int Age);

When you don't want to permit changes to a record variable after it has been initialized, immutability can be useful. Using immutability in a Data Transfer Object (DTO) would be a great example. To guarantee that the object is not altered during transport between a client and a database, use an immutable record as the DTO.

Although non-destructive mutation is permitted, an immutable record type is thread-safe and cannot be altered once it is created. Initializing record types is restricted to constructors only.

Init-only setters

C#9 introduces it-only setters, which can be used in place of sets for properties and indexers. With two exceptions, init accessors and read-only are extremely similar.

Only the object initializer, constructor, or init accessor may set properties with init-only setters;

Once their values are set, they cannot be altered again.

Put another way, while init offers a window for modifying a record's state, after the record is initialized, any properties that were set using init-only setters become read-only.

public record Employee
{
    public int Id { get; init; }
    public string Name { get; init; }
    public int Age { get; init; }
}

Using init-only setters has many benefits, chief among them the avoidance of potential errors that arise when an object argument is passed by reference to a method and its properties are altered for whatever reason.

Using record types

Positional (constructor) arguments allow us to initialize a record's properties.

Defining a record type with parameters for position.

public record Employee(int Id, string Name, int Age);

The Employee record type in the example above uses the properties Id, Name, and Age as positional (constructor) arguments. However, in cases where setting other properties during record initialization is not absolutely necessary, we can also combine nominal and positional declarations.

Declarating nominally and positionally while keeping records.

public record Employee(int Id, string Name, int Age)
{        
    public int Rank { get; init; }
}

The property Rank is not stated as a positional argument in the example above. Therefore, when we create a new instance of Employee, we don't need to set it.

Declarations for nominal and positional values are added to an initial record type.

var emp = new Employee(1, "Jaimin Shethiya", 33);
var emp = new Employee(1, "Jaimin Shethiya", 33) {Rank = 198 };

Built-in formatting for display

In contrast to classes, the compiler-generated record ToString() method, when its properties and fields are public, uses a StringBuilder to display the names and values of an instance.

var emp = new Employee(1, "Jaimin Shethiya", 33) {Rank = 198 };
Console.WriteLine(emp.ToString());

Formatting display

Value Equality

Value equality is the state in which two variables of a record type are equal if and only if all of the fields in both records have the same values for each field.

var emp = new Employee(1, "Jaimin Shethiya", 33);
var emp1 = new Employee(1, "Jaimin Shethiya", 33);

Console.WriteLine(emp.Equales(emp1));

On the other hand, even if two variables of a class type have the same attributes overall, they will not be regarded as equal. This is because class employee reference equality, which states that two variables are only equal if they both refer to the same object.

Value Equality

Inheritance

By deriving from a common base class, inheritance is a powerful notion in object-oriented programming that lets you write reusable code. One of the most interesting things about record types is that they may be extended to extend other record types. As a result, you are able to design a unique record type that is descended from another type (an interface or another record type).

public record PermanentEmployee : Employee
{
    public int NoOfDaysWorked { get; init; }
    public double GrossSalary { get; init; }
    public double Tax { get; init; }
    public double NetSalary { get; init; }
}
var emp = new PermanentEmployee
{
    Id = 1,
    Name = "Jaimin Shethiya",
    Age = 33,
    NoOfDaysWorked = 1250,
    GrossSalary = 18000,
    NetSalary = 16500,
    Tax = 200
};
Console.WriteLine(emp.ToString());

Inheritance

Conclusion

The new record types in C# are fantastic. They facilitate the creation of immutable value types, allowing you to transfer them without fear of immutability. When working with objects that have few attributes and functions, they also aid in reducing the amount of boilerplate code required—especially when you want those objects to be immutable.

We learned the new technique and evolved together.

Happy coding!