C sharp - Record Types and Immutable Data Structures in C#
Record types were introduced in C# 9 as a modern way to define reference types that are primarily used to store data. They are especially useful when you want objects to be immutable and compared based on their values rather than their memory references. This makes them ideal for scenarios such as data transfer objects, configuration models, and functional programming patterns.
1. What Are Record Types
A record is a special kind of class designed for storing data. Unlike traditional classes, records provide built-in functionality for value-based equality, concise syntax, and immutability by default.
A simple example of a record:
public record Person(string Name, int Age);
This single line automatically creates:
-
Properties for Name and Age
-
A constructor
-
Equality comparison logic
-
A readable string representation
In contrast, a class would require much more code to achieve the same behavior.
2. Value-Based Equality
One of the key features of record types is value-based equality. In regular classes, two objects are considered equal only if they reference the same memory location. In records, two instances are equal if their property values are the same.
Example:
var p1 = new Person("John", 30);
var p2 = new Person("John", 30);
Console.WriteLine(p1 == p2); // True
This behavior is useful in applications where logical equality matters more than object identity, such as comparing data records.
3. Immutability
Records encourage immutability, meaning once an object is created, its data cannot be changed. By default, properties in records are init-only, which means they can only be set during object initialization.
Example:
public record Person
{
public string Name { get; init; }
public int Age { get; init; }
}
You can assign values when creating the object, but not modify them later:
var person = new Person { Name = "John", Age = 30 };
// person.Name = "Mike"; // Not allowed
Immutability helps prevent accidental data changes and makes programs easier to reason about, especially in multi-threaded environments.
4. Non-Destructive Mutation (with Expressions)
Although records are immutable, they support creating modified copies using the with expression. This is known as non-destructive mutation.
Example:
var p1 = new Person("John", 30);
var p2 = p1 with { Age = 35 };
Here, p2 is a new object with the updated Age, while p1 remains unchanged.
5. Positional vs Non-Positional Records
There are two ways to define records:
Positional records (compact syntax):
public record Person(string Name, int Age);
Non-positional records (more control):
public record Person
{
public string Name { get; init; }
public int Age { get; init; }
}
Positional records are concise, while non-positional records allow customization such as validation or additional logic.
6. Record vs Class
Key differences between records and classes:
-
Records use value-based equality, classes use reference-based equality
-
Records are immutable by default, classes are mutable by default
-
Records require less boilerplate code
-
Classes are better suited for behavior-heavy objects, while records are ideal for data models
7. Use Cases
Record types are particularly useful in:
-
Data transfer objects (DTOs)
-
API request and response models
-
Configuration settings
-
Functional programming patterns
-
Event sourcing systems
They simplify code and reduce errors related to unintended data modification.
8. Immutable Data Structures
Immutable data structures are objects whose state cannot be changed after creation. Records are a natural fit for building such structures.
Benefits of immutability include:
-
Thread safety without locks
-
Predictable behavior
-
Easier debugging and testing
-
Safer data sharing across components
In C#, immutability can be achieved using:
-
Record types
-
Read-only properties
-
Immutable collections from
System.Collections.Immutable
9. Limitations
While records are powerful, they are not suitable for every situation:
-
They may introduce overhead when copying large objects frequently
-
Not ideal for objects with complex behavior or frequent state changes
-
Mutable records are possible but defeat the purpose of immutability
Conclusion
Record types in C# provide a concise and powerful way to model immutable data with built-in value-based equality. They reduce boilerplate code and align well with modern programming practices that emphasize safety, clarity, and maintainability. By using records and immutable data structures, developers can create more robust and predictable applications.