Record Types and Immutable Objects in C#
Learn C# record types and immutable objects, including value equality, with-expressions, and patterns for building safer data models.
Introduced with C# 9, record types make it easier to create immutable objects. Record types, like classes, can contain both data and behavior; however, their primary purpose is to define data-carrying objects (data models, DTOs, etc.) in a simpler, safer, and more readable way. Immutable objects are those whose values cannot be changed after creation. This approach improves safety in multi-threading scenarios and reduces the risk of errors.
What Is a Record?
The record keyword is used to define data types similar to classes or structs.
By default, records provide value-based equality.
That means if the fields of two record objects are the same, the objects are considered equal.
public record Person(string Name, int Age);
class Program
{
static void Main()
{
var p1 = new Person("John", 30);
var p2 = new Person("John", 30);
Console.WriteLine(p1 == p2); // True (value-based comparison)
}
}
Immutable Properties
In record types, properties can be defined with the init accessor by default.
This allows values to be assigned when the object is created, but prevents modification afterward.
public record Product
{
public string Name { get; init; } = string.Empty;
public decimal Price { get; init; }
}
class Program
{
static void Main()
{
var p = new Product { Name = "Pen", Price = 12.5m };
Console.WriteLine($"{p.Name} - {p.Price} USD");
// p.Price = 15m; // Compilation error! (init-only property)
}
}
The with Expression
With record types, the with expression can be used to create a copy of an existing object
while changing only the desired fields. This is very useful when working with immutable objects.
var p1 = new Product { Name = "Notebook", Price = 45m };
// Create a copy with the with expression and change the Price
var p2 = p1 with { Price = 40m };
Console.WriteLine(p1.Price); // 45
Console.WriteLine(p2.Price); // 40
Record Class vs. Record Struct
With C# 10, record struct support was added.
This means that a record can be defined not only as a class-based type but also as a value type.
record class is a reference type, while record struct is a value type.
public record struct Point(int X, int Y);
class Program
{
static void Main()
{
var a = new Point(1, 2);
var b = new Point(1, 2);
Console.WriteLine(a == b); // True (value-based equality)
}
}
Advantages of Immutable Objects
- Thread-Safe: Objects are safe in multi-threaded environments since they cannot change.
- No Side Effects: Suitable for functional programming; the same input always produces the same output.
- Easier Debugging: Since the object’s state never changes, debugging is simpler.
- Code Readability: Data models are defined in a clearer and more concise way.
Example: Immutable Data Model
In the example below, the Order object is defined as immutable and
new versions are created using the with expression.
public record Order(int Id, string Customer, decimal Amount);
class Program
{
static void Main()
{
var o1 = new Order(1, "John", 500m);
Console.WriteLine(o1);
// Create a copy with the with expression
var o2 = o1 with { Amount = 600m };
Console.WriteLine(o2);
}
}
// Output:
Order { Id = 1, Customer = John, Amount = 500 }
Order { Id = 1, Customer = John, Amount = 600 }
TL;DR
record: New type that provides value-based equality.initproperties: Assigned during object creation, cannot be modified later.withexpression: Used to derive copies from immutable objects.record class= reference type,record struct= value type.- Immutable objects are thread-safe, reliable, and provide a readable model.
Related Articles
Class, Object, Property and Methods in C#
Learn how classes, objects, properties, and methods work in C# and form the core building blocks of object-oriented programming.
Encapsulation, Inheritance, and Polymorphism in C#
Learn encapsulation, inheritance, and polymorphism in C# with clear examples to understand core OOP principles and real use cases.