C# 9.0 Record Types in Detail

C# 9.0 Record Types in Detail

In this article, I am going to explain Record types which have been introduced in C# 9.0.

C# 9.0 introduces record types. You use the record keyword to define a reference type that provides built-in functionality for encapsulating data.

Record Types are a popular feature of F#, also commonly found in other functional languages, that have recently been introduced to C# in version 9.0 (released alongside .Net 5).

Prerequisites

.NET 5.0

Visual Studio 2019 (V 16.8, Preview 3)

While records can be mutable, they are primarily intended for supporting immutable data models. The record type offers the following features:

• Concise syntax for creating a reference type with immutable properties

• Behavior useful for a data-centric reference type:

 o    Value equality

 o    Concise syntax for nondestructive mutation

 o    Built-in formatting for display

• Support for inheritance hierarchies

Creating a Record Type

Let’s see how to create a Record Type in C#, its almost same as we create Class type in C#. Only difference is Record Type is immutable;

public record Employee
    {
        public int EmpId { get; init; }
        public string EmployeeName { get; init; }
        public int Age { get; init; }

        public string Department { get; init; }
        public decimal Salary { get; init; }       

    }

    public record Address
    {
        public int HouseNumber { get; init; }
        public string StreeAddress { get; init; }
        public int PinCode { get; init; }
    }

Instantiating a Record Type

Record Types are instantiated like any standard class. The thing to note is that the constructor is auto generated for you in the order you supply the properties when using Positional Record syntax to declare the type

class Program
    {
        static void Main(string[] args)
        {
            Employee emp1 = new Employee
            {
                EmpId = 1,
                EmployeeName = "Dhiraj Kumar",
                Age = 37,
                Department = "Technology",
                Salary = 1233
            };
            Console.WriteLine("Hello World!");
        }
    }

With-expressions

When working with immutable data, a common pattern is to create new values from existing ones to represent a new state. For instance, if our employee were to change their name we would represent it as a new object that’s a copy of the old one, except with a different name. This technique is often referred to as non-destructive mutation. Instead of representing the employee over time, the record represents the employee’s state at a given time. To help with this style of programming, records allow for a new kind of expression; the with-expression:

class Program
    {
        static void Main(string[] args)
        {
            Employee emp1 = new Employee
            {
                EmpId = 1,
                EmployeeName = "Dhiraj Kumar",
                Age = 37,
                Department = "Technology",
                Salary = 1233
            };
// by using with keyword , new employee object is created with less code
            Employee emp2 = emp1 with { EmployeeName = "Paresh Kale" };

            Console.WriteLine("Hello World!");
        }
    }

Value-based equality

All objects inherit a virtual Equals(object) method from the object class. This is used as the basis for the Object.Equals(object, object) static method when both parameters are non-null. Structs override this to have "value-based equality", comparing each field of the struct by calling Equals on them recursively. Records do the same. This means that in accordance with their "value-ness" two record objects can be equal to one another without being the same object. For instance if we modify the last name of the modified employee back again:

var emp2 = emp1 with { Name = "Paresh Kale" };

We would now have ReferenceEquals(emp1, emp2) = false (they aren’t the same object) but Equals(emp1, emp2) = true (they have the same value).

Along with the value-based Equals there’s also a value-based GetHashCode() override to go along with it. Additionally, records implement IEquatable<T> and overload the == and != operators, so that the value-based behavior shows up consistently across all those different equality mechanisms.

Value equality and mutability don’t always mesh well. One problem is that changing values could cause the result of GetHashCode to change over time, which is unfortunate if the object is stored in a hash table! We don’t disallow mutable records, but we discourage them unless you have thought through the consequences!

Inheritance

Records can inherit from other records:

namespace CSharpRecordTypes
{
    public record Employee : Address
    {
        public int EmpId { get; init; }
        public string EmployeeName { get; init; }
        public int Age { get; init; }
        public string Department { get; init; }
        public decimal Salary { get; init; }

    }

    public record Address
    {
        public int HouseNumber { get; init; }
        public string StreeAddress { get; init; }
        public int PinCode { get; init; }
    }
}

Positional records

Sometimes it’s useful to have a more positional approach to a record, where its contents are given via constructor arguments, and can be extracted with positional deconstruction. It’s perfectly possible to specify your own constructor and deconstructor in a record:

public record Employee : Address
    {
        public int EmpId { get; init; }
        public string EmployeeName { get; init; }
        public int Age { get; init; }
        public string Department { get; init; }
        public decimal Salary { get; init; }

        public Employee(int empId,string empName) 
            => (EmpId, EmployeeName) = (empId, empName);


    }

But there’s a much shorter syntax for expressing exactly the same thing (modulo casing of parameter names):

public record Employee(int empId, string empName);

This declares the public init-only auto-properties and the constructor and the deconstructor, so that you can write:

var emp = new Employee(1, "Dhiraj Kumar"); // positional construction

Now it will look like

class Program
    {
        static void Main(string[] args)
        {
            Employee emp1 = new Employee(4,"Apurwa Sharma")
            {
                EmpId = 1,
                EmployeeName = "Dhiraj Kumar",
                Age = 37,
                Department = "Technology",
                Salary = 1233
            };
            Employee emp3 = new Employee(2, "Aniket Arya");
            Employee emp2 = emp1 with { EmployeeName = "Paresh Kale" };

            Console.WriteLine("Hello World!");
        }
    }

That's all for this Blog developers and with that, it's a wrap! I hope you found the article useful.

I create content about Programming, Mentoring and Productivity. If this is something that interests you, please share the article with your friends and connections.

Thank you for reading, If you have reached so far, please like the article, It will encourage me to write more such articles. Do share your valuable suggestions, I appreciate your honest feedback!

I would strongly recommend you to Check out my YouTube Channel where I post programming video and don't forget to subscribe to my Channel. I would love to connect with you at Twitter | Instagram

You can find all code at GitHub

See you in my next Blog article, Take care!!