C# tutorials > Core C# Fundamentals > Data Structures and Collections > What are records in C# 9.0 and later?
What are records in C# 9.0 and later?
Records in C# 9.0 and later provide a concise syntax for creating immutable data types. They are reference types that offer value-based equality, easy deconstruction, and a `ToString` method that displays property names and values. Records are particularly useful for data transfer objects (DTOs) and representing immutable state.
Basic Record Definition
This code defines a simple record named `Person` with two properties: `FirstName` and `LastName`. The properties are defined within the record's constructor, making them immutable after creation. This is a positional record definition which also creates a constructor.
public record Person(string FirstName, string LastName);
Creating and Using a Record
This code shows how to create an instance of the `Person` record and access its properties. Because the properties are defined as part of the primary constructor, the `new` keyword is used along with the provided values.
Person person1 = new Person("John", "Doe");
Console.WriteLine(person1.FirstName); // Output: John
Console.WriteLine(person1.LastName); // Output: Doe
Value-Based Equality
Records automatically implement value-based equality. This means that two record instances are considered equal if their properties have the same values, regardless of whether they are different objects in memory. This is a significant advantage over classes, where you would typically need to override the `Equals` method to achieve this behavior.
Person person1 = new Person("John", "Doe");
Person person2 = new Person("John", "Doe");
Console.WriteLine(person1 == person2); // Output: True
Deconstruction
Records automatically support deconstruction, allowing you to easily extract the values of their properties into separate variables. The syntax is similar to tuple deconstruction. The parentheses after `var` indicate that the values are being assigned to individual variables.
Person person1 = new Person("John", "Doe");
var (firstName, lastName) = person1;
Console.WriteLine(firstName); // Output: John
Console.WriteLine(lastName); // Output: Doe
ToString() Override
Records override the `ToString()` method to provide a user-friendly string representation of the record's properties and their values. This is helpful for debugging and logging.
Person person1 = new Person("John", "Doe");
Console.WriteLine(person1); // Output: Person { FirstName = John, LastName = Doe }
Record Inheritance
Records can inherit from other records (or classes). This example shows a `Student` record inheriting from the `Person` record. The Student record defines a new `Major` property and passes the `FirstName` and `LastName` to the base class constructor.
public record Student(string FirstName, string LastName, string Major) : Person(FirstName, LastName);
Student student1 = new Student("Jane", "Smith", "Computer Science");
Console.WriteLine(student1); // Output: Student { FirstName = Jane, LastName = Smith, Major = Computer Science }
Record with Properties (Non-Positional)
Records can also be defined with traditional properties instead of using positional syntax. The `init` accessor ensures that properties can only be set during object initialization, maintaining immutability after creation. This is useful for scenarios where you want more control over the property definitions or need to perform custom validation logic during initialization.
public record Employee
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
Using `with` expression for non-destructive mutation
The `with` expression allows you to create a new record instance based on an existing one, but with some properties modified. This is a form of non-destructive mutation, as the original record remains unchanged. In this example, `person2` is a new `Person` record that is identical to `person1`, except for the `City` in the nested `Address` record. Note the chained `with` expressions to modify the inner object. Using `with` for immutable objects allows you to have changes without modifying the source object.
public record Address(string Street, string City, string ZipCode);
public record Person(string FirstName, string LastName, Address Address);
Person person1 = new Person("John", "Doe", new Address("123 Main St", "Anytown", "12345"));
Person person2 = person1 with { Address = person1.Address with { City = "Newville" } };
Console.WriteLine(person1.Address.City); // Output: Anytown
Console.WriteLine(person2.Address.City); // Output: Newville
Real-Life Use Case: Data Transfer Objects (DTOs)
Records are ideal for creating DTOs, which are used to transfer data between different layers of an application. Their immutability ensures that the data remains consistent throughout the process. For example, a DTO representing customer data can be easily created using a record, promoting a cleaner and more robust architecture.
Best Practices
with
expression to create updated versions of records without modifying the original instances.
Interview Tip
Be prepared to explain the difference between records and classes in C#. Highlight the value-based equality, immutability, and concise syntax offered by records. Also, understand how the with
expression enables non-destructive mutation.
When to Use Them
Use records when you need:ToString()
implementation.
Memory Footprint
The memory footprint of a record is generally similar to that of a class with the same properties. Records are reference types, so they are stored on the heap. The key difference is that records often encourage immutability, which can lead to more memory usage due to the creation of new instances when data changes. However, this can be offset by improved data integrity and reduced risk of unintended side effects.
Alternatives
Alternatives to records include:
Pros
ToString()
implementation.
Cons
FAQ
-
Are records reference types or value types?
Records are reference types, similar to classes. They are stored on the heap, and variables of record types hold references to the objects in memory. -
Can I have mutable properties in a record?
Yes, you can have mutable properties in a record, but it's generally recommended to make them immutable using the `init` accessor or by making the record completely immutable. Immutable records are simpler to reason about and less prone to errors. -
How does the `with` expression work?
The `with` expression creates a new record instance with the same values as the original, except for the specified properties, which are updated with new values. It performs a shallow copy of the object, so any reference type properties will still point to the same objects in memory unless explicitly changed in the `with` expression. -
Can I use records in older versions of C#?
No, records were introduced in C# 9.0. To use records, you need to use a compiler that supports C# 9.0 or later. You might be able to simulate some of the behavior with custom classes, but they will require more code to achieve the same functionality.