DEV Community

Cover image for Exploring Records in C#.
Spyros Ponaris
Spyros Ponaris

Posted on

Exploring Records in C#.

Introduction

C# 9.0 introduced a powerful new feature: record. Designed to simplify immutable object creation, records prioritize data over identity, making them ideal for scenarios where value-based equality is essential. In this post, we'll dive into what records are, how to use them, their pros and cons, and practical use cases to help you decide when to use them in your projects.


What Are Records in C#?

A record is a special kind of class or struct designed to work seamlessly with immutable (read-only) data. Its most compelling feature is nondestructive mutation, allowing you to create modified copies of objects without altering the original. Beyond this, records excel in scenarios where you need types that simply hold or combine data.

In simple use cases, records eliminate boilerplate code while adhering to equality semantics ideal for immutable types. Whether you're working with data transfer objects (DTOs) or configuration models, records offer a clean and concise way to represent data.


Defining a Record

Defining a record is similar to defining a class or struct. Records can include fields, properties, methods, and more. They can implement interfaces and, in the case of class-based records, even inherit from other class-based records.


public record Person(string FirstName, string LastName);

This creates an immutable Person record with the following features:

  • Primary Constructor: Automatically initializes FirstName and LastName.
  • Value-Based Equality: Two Person records with the same values are considered equal.
  • Auto-Generated ToString(): Provides a human-readable string representation.

Features of Records

1. Immutability by Default

Records prioritize immutability, ensuring that data can't be modified after creation. With init accessors, you can initialize properties only during object creation:


public record Car
{
    public string Make { get; init; }
    public string Model { get; init; }
}

2. Value-Based Equality

Unlike classes that rely on reference-based equality, records compare property values:


var car1 = new Car { Make = "Tesla", Model = "Model S" };
var car2 = new Car { Make = "Tesla", Model = "Model S" };

Console.WriteLine(car1 == car2); // True

3. Nondestructive Mutation

With the with expression, you can create a modified copy of an existing record:


var car3 = car1 with { Model = "Model 3" };
Console.WriteLine(car3); // Car { Make = Tesla, Model = Model 3 }

4. Deconstruction

Records support deconstruction for easy extraction of properties:


var (make, model) = car1;
Console.WriteLine($"{make} {model}"); // Tesla Model S

5. Inheritance and Interfaces

Records can implement interfaces and inherit from other records:


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

When to Use Records

Use Cases:

  • Data Transfer Objects (DTOs): Simplify objects passed between layers.
  • Immutable Configuration: Store settings or configurations that shouldn't change.
  • Functional Programming: Handle immutable state and transformations effectively.
  • Representing Value-Based Data: E.g., geographic coordinates, complex numbers, etc.

Pros and Cons of Records

Pros

  • Simplified Syntax: Primary constructors reduce boilerplate code.
  • Immutability: Ensures consistent and predictable data handling.
  • Value-Based Equality: Automatically handles equality logic for properties.
  • Nondestructive Mutation: with expressions make updates cleaner and safer.
  • Readability: Auto-generated ToString() improves debugging and logging.

Cons

  • Limited to C# 9.0+: Requires .NET 5 or later, making it unavailable in older projects.
  • Learning Curve: Developers new to immutability might need time to adjust.
  • Performance Overhead: Extra work is required for equality checks in large objects.
  • Inheritance Limitations: Records only support inheritance within other records, not classes or structs.
  • Not Always Necessary: Overhead may not justify use in simple scenarios.

Comparison: Classes vs. Records vs. Structs

Feature Class Struct Record
Immutability Optional Optional Default
Equality Reference-Based Value-Based Value-Based
Inheritance Full Support None Limited to Records
Performance Reference Type (Heap) Value Type (Stack) Reference Type (Heap)
Syntax Verbose Concise Concise with Primary Constructor

Conclusion

Records introduce a new way to define types in C#, complementing the existing class and struct definitions. Here's how and when to use each:

  • Class Types: Use class definitions to create object-oriented hierarchies that focus on the responsibilities and behavior of objects. Classes emphasize reference-based equality and are ideal for encapsulating both state and behavior.
  • Struct Types: Use struct types for lightweight data structures that primarily store data and are small enough to be copied efficiently. Structs emphasize value-based equality and are best suited for scenarios where copying the entire object is inexpensive.
  • Record Types: Use record types when you want value-based equality and comparison, immutability, and the convenience of reference semantics. Records are perfect for scenarios where objects represent data and you don’t want to manually implement equality logic.
  • Record Struct Types: Use record struct types when you want the benefits of records—such as value-based equality and immutability—but for types that are small enough to copy efficiently. These are ideal for high-performance scenarios involving value semantics.



References

Top comments (0)