Records in C#



Records in C# are reference types introduced in C# 9.0 that are used to store immutable data. Immutable data means that once a record is created, its values cannot be modified. It is similar to a class but is mainly used for holding and comparing data instead of defining behavior.

Records automatically provide features like value-based equality, hash code generation, and a readable string representation which make them easier to use for data storage and transfer.

Syntax for Records

A record is defined using the record keyword, followed by the record name and its parameters inside parentheses. This is called a positional record

record RecordName(Type Property1, Type Property2, ...);

A record can also be defined like a regular class with a body −

public record RecordName
{
    public Type Property1 { get; init; }
    public Type Property2 { get; init; }
}

In this syntax, record is the keyword for defining a record, RecordName is the name of the record, and Property1, Property2 are its properties. The init keyword makes the properties immutable after object creation.

Creating Record Objects

We can create objects of a record using either regular property initialization or positional parameters −


// Using regular property initialization
var person1 = new Person { Name = "Ritu", Age = 25 };

// Using positional parameters
person1 = new Person("Ritu", 28);

Once created a record's properties cannot be changed because records support init-only properties meaning we can assign values when creating the object but not change them later.

// This will give an error
person1.Name = "Anita"; // Not allowed

Example

Here, we create a simple record to hold information about a person. For that we create a Person record with two properties, Name and Age.

Note that when we print person1, it automatically gives a readable string representation because records generate a default ToString() method.
using System;

public record Person
{
    public string Name { get; init; }
    public int year { get; init; }
}

class Program
{
    static void Main()
    {
        var person1 = new Person { Name = "George", year = 25 };
        Console.WriteLine(person1);
    }
}

Following is the output of the above program which shows the record data in a readable format.

Person { Name = George, Age = 25 }

Positional Records in Detail

Positional records are a shorter way to define records. Using positional syntax, C# automatically generates properties and a constructor. The ToString() method is also created automatically, so we can print the record in a readable format.

Example

In this example, we create a positional record Person with two properties and create the object for Person record, Then we defined the constructor to set the values and then we print the values.

using System;

public record Person(string Name, int Age);

class Program
{
    static void Main()
    {
        var person = new Person("Ritu", 25);
        Console.WriteLine(person);
    }
}

Following is the output of the program which shows the record with its property values.

Person { Name = Ritu, Age = 25 }

Value-Based Equality

Records are different from classes because they are compared by values instead of references. This means two records with the same property values are considered equal, even if they are two different objects in memory.

Example

In this example, we create two record objects, person1 and person2 with the same property values. When we compare them using == or .Equals(), both return true because records use value-based equality.

using System;

public record Person
{
    public string Name { get; init; }
    public int Age { get; init; }
}

class Program
{
    static void Main()
    {
        var person1 = new Person { Name = "Aarav", Age = 25 };
        var person2 = new Person { Name = "Aarav", Age = 25 };

        Console.WriteLine(person1 == person2);        // True
        Console.WriteLine(person1.Equals(person2));   // True
    }
}

Below is the output of the program which shows that both records are equal by value.

True
True

With-Expressions

Records are immutable, so their properties cannot be changed after creation. To update data, we can create a new record object from an existing one using a with-expression.

The with keyword creates a new record based on an existing object. It allows us to modify some properties while keeping the original record unchanged.

Example

In this example, we create a record object person1 with some properties. Then we create another record object person2 using the with keyword. This copies all the properties of person1 and changes the Name to "Riya", keeping the record object person1 unchanged.

using System;

public record Person
{
    public string Name { get; init; }
    public int Age { get; init; }
}

class Program
{
    static void Main()
    {
        var person1 = new Person { Name = "Aarav", Age = 25 };

        // Creating a new record with a modified Name
        var person2 = person1 with { Name = "Riya" };

        Console.WriteLine(person1); // Person { Name = Aarav, Age = 25 }
        Console.WriteLine(person2); // Person { Name = Riya, Age = 25 }
    }
}

Following is the output of the program showing the original record and the modified copy.

Person { Name = Aarav, Age = 25 }
Person { Name = Riya, Age = 25 }

Record Inheritance

Records support inheritance and can inherit properties from other records. A derived record includes all properties of the base record and can also define its own. Even with inheritance, records keep immutability and value-based equality.

Example

In this example we create a base record Person and an Employee record that inherits from Person record and adds a Department property. Then we create an Employee object to display all the values, including the inherited ones.

using System;

public record Person(string Name, int Age);

public record Employee(string Name, int Age, string Department) : Person(Name, Age);

class Program
{
    static void Main()
    {
        var employee = new Employee("Aarav", 25, "IT");
        Console.WriteLine(employee);
    }
}

Following is the output of the program showing the inherited record and its properties.

Employee { Name = Aarav, Age = 25, Department = IT }

Records vs Classes

Classes and records both define objects in C# but they work in different ways. Classes are used for objects that can change after creation and can include behavior. Two class objects are considered different even if their properties are the same.

Records, on the other hand, are used to store immutable data and compare objects by their values. Two record objects with the same property values are considered equal, even if they are different instances in memory.

Example Using Class

Let's first see how a class behaves with equality. Here, we create two objects of the class PersonClass with the same property values. Even though person1 and person2 have the same values, classes use reference equality, so both == and .Equals() return false.

using System;

public class PersonClass
{
    public string Name { get; set; }
    public int Age { get; set; }
}

class Program
{
    static void Main()
    {
        var person1 = new PersonClass { Name = "Aarav", Age = 25 };
        var person2 = new PersonClass { Name = "Aarav", Age = 25 };

        Console.WriteLine(person1 == person2);        // False
        Console.WriteLine(person1.Equals(person2));   // False
    }
}

Following is the output of the program showing that the two class objects are not equal.

False
False

Example Using Record

Now, let's see how records handle the same data. Here, we create two objects of the record PersonRecord with the same property values. Records use value-based equality, so person1 and person2 are considered equal, and both == and .Equals() return true.

using System;

public record PersonRecord
{
    public string Name { get; init; }
    public int Age { get; init; }
}

class Program
{
    static void Main()
    {
        var person1 = new PersonRecord { Name = "Aarav", Age = 25 };
        var person2 = new PersonRecord { Name = "Aarav", Age = 25 };

        Console.WriteLine(person1 == person2);        // True
        Console.WriteLine(person1.Equals(person2));   // True
    }
}

Following is the output of the program showing that the two record objects are equal by value.

True
True

Key Differences Between Records and Classes

The following table shows the main differences between records and classes in C#.

Feature Record Class
Equality Value-based Reference-based
Immutability Immutable by default (init) Mutable (can change properties)
ToString() Auto-generated readable string Shows type name only
Use Case Data storage, DTOs Business logic, methods
Modification Use with to create copies Can modify properties directly

Record Structs (C# 10 and Later)

In C# 10, we can create record structs, which behave like value types instead of reference types. They are useful for small, simple data objects and automatically provide value-based equality and readable output.

Example

Here's an example of how we can create a record struct. We define a record struct Point with two properties, X and Y. Then, we print the object.

using System;

// We define a record struct Point with two properties, X and Y
public record struct Point(int X, int Y);

class Program
{
    static void Main()
    {
        // Create a record struct object
        var p1 = new Point(5, 10);

        // Print the object
        Console.WriteLine(p1);
    }
}

Below is the output of the above program.

Point { X = 5, Y = 10 }

Conclusion

In this chapter, we learned that records in C# store immutable data by default and focus on value-based equality rather than reference equality. In updated versions of C#, we can also use features like deconstructors, inheritance, record structs and more.

Advertisements