DEV Community

mohamed Tayel
mohamed Tayel

Posted on

c# advance : Introduction to Records in C#

Meta Description:

Discover the power of records in C#. Learn how to create immutable, data-focused types with value-based equality, compact syntax, and built-in functionality. Explore their usage, limitations, and examples to simplify your C# development.

The record type is a significant addition to C#, offering a streamlined way to create reference types with minimal boilerplate while packing in plenty of useful functionality. Records are often used as a compact alternative to classes, especially when working with immutable data structures or scenarios that benefit from value-based equality.

In this article, we’ll explore the concept of records, how they differ from classes, and why they are a powerful tool in C#. By the end, you’ll understand the fundamentals of records, including their syntax, generated code, and best-use scenarios.


What Are Records?

A record in C# is a reference type designed to simplify the definition of data objects. Unlike a traditional class, a record includes features like value-based equality and immutability by default. While records can be used in similar scenarios to classes, their focus is on representing data rather than encapsulating behavior.

Key Features of Records:

  • Compact Syntax: Records require much less code than equivalent classes.
  • Value-Based Equality: Records compare the values of properties rather than references.
  • Immutability: Properties defined in the primary constructor are immutable by default.
  • Automatic Implementations: Records automatically generate useful methods such as ToString, Equals, and a copy constructor.

Creating a Record

A record is defined using the record keyword, which represents a reference type by default (e.g., record class). You can also specify record struct for value types. Here's how to define a record:

public record Employee(string Name, string Role, int Salary);
Enter fullscreen mode Exit fullscreen mode

This is known as a positional record. The properties (Name, Role, and Salary) are defined directly in the primary constructor, making the syntax concise and intuitive. Behind the scenes, this generates:

  1. Properties for each parameter.
  2. A constructor requiring all parameters.
  3. A class with value-based equality and immutability.

Creating an Instance of a Record

var employee = new Employee("Alice", "Developer", 80000);
Console.WriteLine(employee); 
// Output: Employee { Name = Alice, Role = Developer, Salary = 80000 }
Enter fullscreen mode Exit fullscreen mode

Generated Code Behind Records

Properties and Backing Fields

Records generate read-only properties for all parameters defined in the primary constructor, ensuring immutability unless explicitly modified.

Value-Based Equality

Records implement the IEquatable interface, providing value-based equality out of the box. For example:

var emp1 = new Employee("Alice", "Developer", 80000);
var emp2 = new Employee("Alice", "Developer", 80000);

Console.WriteLine(emp1 == emp2); // Output: True
Enter fullscreen mode Exit fullscreen mode

This behavior differs from classes, where equality checks are reference-based by default.

Deconstructor

A record includes a deconstructor, allowing its properties to be extracted easily:

var (name, role, salary) = employee;
Console.WriteLine($"Name: {name}, Role: {role}, Salary: {salary}");
// Output: Name: Alice, Role: Developer, Salary: 80000
Enter fullscreen mode Exit fullscreen mode

Copy Constructor with with

Records support copying with modifications using the with expression:

var seniorEmployee = employee with { Role = "Senior Developer", Salary = 90000 };
Console.WriteLine(seniorEmployee);
// Output: Employee { Name = Alice, Role = Senior Developer, Salary = 90000 }
Enter fullscreen mode Exit fullscreen mode

Inheritance and Equality Contracts

Records support inheritance, but their equality contract ensures that two records of different types are never equal, even if their properties match:

public record Manager(string Name, string Role, int Salary, int TeamSize) 
    : Employee(Name, Role, Salary);

var employee = new Employee("Alice", "Developer", 80000);
var manager = new Manager("Alice", "Developer", 80000, 10);

Console.WriteLine(employee == manager); // Output: False
Enter fullscreen mode Exit fullscreen mode

This behavior is crucial to understand when working with derived records.


Immutability Caveats

While records enforce immutability for properties in the primary constructor, reference-type properties are not deeply immutable. For example:

public record Project(string Title, List<string> Tasks);

var project = new Project("New Website", new List<string> { "Design", "Develop" });
project.Tasks.Add("Test"); // The list can be modified!
Console.WriteLine(string.Join(", ", project.Tasks)); 
// Output: Design, Develop, Test
Enter fullscreen mode Exit fullscreen mode

Here, the reference to Tasks is immutable, but the contents of the list can still change. To ensure deep immutability, use immutable collections or custom logic.


When to Use Records

Records are ideal for scenarios where:

  • You want concise and immutable data structures.
  • Value-based equality is essential (e.g., domain models, DTOs).
  • You need built-in functionality like ToString and with.

When Not to Use Records

  • Avoid using records for Entity Framework entities, as EF relies on reference equality and mutability for change tracking.
  • Records are not suitable for classes handling significant business logic.

Summary

The record type in C# provides a concise and powerful way to define data structures with value-based equality, immutability, and minimal boilerplate. By understanding how records work, including their generated code and limitations, you can leverage them effectively in your applications.


Key Takeaways

  1. Records simplify the creation of immutable types with built-in functionality.
  2. They are best used for representing data, not business logic.
  3. Value-based equality and with expressions make records ideal for scenarios involving data comparison and copying.

Records are a welcome addition to C# for developers aiming to write clean and maintainable code. As you explore their capabilities, you’ll find that they make handling data-focused scenarios not only easier but also more expressive.

Top comments (1)

Collapse
 
zinklof profile image
Zink

This is actually a really nice way of explaining things. I’ve not managed to find myself using records in my work really at all but you’ve got me looking for excuses to do so more now lol!