Records are a feature in C# designed to simplify the creation of immutable types and value-like behavior with reference semantics. Introduced in C# 9, records provide a succinct syntax for defining data models and are particularly useful for scenarios where immutability and equality are important.
Records are reference types, similar to classes, but are designed to provide value semantics. This means that two record instances with the same data are considered equal. Records are ideal for defining data models, especially when the data is immutable.
Example:
namespace RecordExamples;
public record Person(string FirstName, string LastName);
class Program
{
static void Main(string[] args)
{
var person1 = new Person("John", "Doe");
var person2 = new Person("John", "Doe");
Console.WriteLine(person1 == person2); // Output: True
}
}
Records have a concise syntax and come with several built-in features, such as value equality, with-expressions, and deconstruction.
namespace RecordExamples;
// Define a record with primary constructor and properties
public record Person(string FirstName, string LastName)
{
// Additional properties or methods can be added here
public string FullName => $"{FirstName} {LastName}";
}
class Program
{
static void Main(string[] args)
{
var person = new Person("Jane", "Doe");
// Value equality
var anotherPerson = new Person("Jane", "Doe");
Console.WriteLine(person == anotherPerson); // Output: True
// With-expression
var modifiedPerson = person with { LastName = "Smith" };
Console.WriteLine(modifiedPerson.FullName); // Output: Jane Smith
// Deconstruction
var (firstName, lastName) = person;
Console.WriteLine($"FirstName: {firstName}, LastName: {lastName}"); // Output: FirstName: Jane, LastName: Doe
}
}
Records are designed to be immutable by default, meaning their properties cannot be modified after creation. However, C# 10 introduced with-expressions for non-destructive mutation, allowing you to create modified copies of records.
Example:
namespace RecordExamples;
// Define a record
public record Person(string FirstName, string LastName);
class Program
{
static void Main(string[] args)
{
var person = new Person("Alice", "Johnson");
// Use with-expression to create a modified copy
var modifiedPerson = person with { LastName = "Smith" };
Console.WriteLine(modifiedPerson.FirstName); // Output: Alice
Console.WriteLine(modifiedPerson.LastName); // Output: Smith
}
}
Records support inheritance, allowing you to create hierarchies of records. When inheriting from a record, you must use the sealed
modifier to prevent further inheritance, or explicitly allow it.
namespace RecordExamples;
// Base record
public record Person(string FirstName, string LastName);
// Derived record
public record Student(string FirstName, string LastName, string StudentId) : Person(FirstName, LastName);
class Program
{
static void Main(string[] args)
{
var student = new Student("Tom", "Brown", "S12345");
Console.WriteLine(student); // Output: Student { FirstName = Tom, LastName = Brown, StudentId = S12345 }
}
}
Positional records provide a compact syntax for defining records with positional parameters in the primary constructor. This approach is especially useful for simple data carriers.
Example:
namespace RecordExamples;
// Define a positional record
public record Point(int X, int Y);
class Program
{
static void Main(string[] args)
{
var point = new Point(3, 4);
// Value equality
var anotherPoint = new Point(3, 4);
Console.WriteLine(point == anotherPoint); // Output: True
// Deconstruction
var (x, y) = point;
Console.WriteLine($"X: {x}, Y: {y}"); // Output: X: 3, Y: 4
}
}
Records in C# are a powerful feature that simplifies the creation of immutable types with value semantics. By understanding their syntax, features, and best practices, you can effectively use records to build clean and efficient data models in your applications.