Introduction to record types in C#
A record in C# is a class or struct that provides special syntax and behavior for working with data models. The record
modifier instructs the compiler to synthesize members that are useful for types whose primary role is storing data. These members include an overload of ToString() and members that support value equality.
When to use records
Consider using a record in place of a class or struct in the following scenarios:
- You want to define a data model that depends on value equality.
- You want to define a type for which objects are immutable.
Value equality
For records, value equality means that two variables of a record type are equal if the types match and all property and field values compare equal. For other reference types such as classes, equality means reference equality by default, unless value equality was implemented. That is, two variables of a class type are equal if they refer to the same object. Methods and operators that determine equality of two record instances use value equality.
Not all data models work well with value equality. For example, Entity Framework Core depends on reference equality to ensure that it uses only one instance of an entity type for what is conceptually one entity. For this reason, record types aren't appropriate for use as entity types in Entity Framework Core.
Immutability
An immutable type is one that prevents you from changing any property or field values of an object after it's instantiated. Immutability can be useful when you need a type to be thread-safe or you're depending on a hash code remaining the same in a hash table. Records provide concise syntax for creating and working with immutable types.
Immutability isn't appropriate for all data scenarios. Entity Framework Core, for example, doesn't support updating with immutable entity types.
How records differ from classes and structs
The same syntax that declares and instantiates classes or structs can be used with records. Just substitute the class
keyword with the record
, or use record struct
instead of struct
. Likewise, the same syntax for expressing inheritance relationships is supported by record classes. Records differ from classes in the following ways:
- You can use positional parameters in a primary constructor to create and instantiate a type with immutable properties.
- The same methods and operators that indicate reference equality or inequality in classes (such as Object.Equals(Object) and
==
), indicate value equality or inequality in records. - You can use a
with
expression to create a copy of an immutable object with new values in selected properties. - A record's
ToString
method creates a formatted string that shows an object's type name and the names and values of all its public properties. - A record can inherit from another record. A record can't inherit from a class, and a class can't inherit from a record.
Record structs differ from structs in that the compiler synthesizes the methods for equality, and ToString
. The compiler synthesizes a Deconstruct
method for positional record structs.
The compiler synthesizes a public init-only property for each primary constructor parameter in a record class
. In a record struct
, the compiler synthesizes a public read-write property. The compiler doesn't create properties for primary constructor parameters in class
and struct
types that don't include record
modifier.
Examples
The following example defines a public record that uses positional parameters to declare and instantiate a record. It then prints the type name and property values:
public record Person(string FirstName, string LastName);
public static class Program
{
public static void Main()
{
Person person = new("Nancy", "Davolio");
Console.WriteLine(person);
// output: Person { FirstName = Nancy, LastName = Davolio }
}
}
The following example demonstrates value equality in records:
public record Person(string FirstName, string LastName, string[] PhoneNumbers);
public static class Program
{
public static void Main()
{
var phoneNumbers = new string[2];
Person person1 = new("Nancy", "Davolio", phoneNumbers);
Person person2 = new("Nancy", "Davolio", phoneNumbers);
Console.WriteLine(person1 == person2); // output: True
person1.PhoneNumbers[0] = "555-1234";
Console.WriteLine(person1 == person2); // output: True
Console.WriteLine(ReferenceEquals(person1, person2)); // output: False
}
}
The following example demonstrates use of a with
expression to copy an immutable object and change one of the properties:
public record Person(string FirstName, string LastName)
{
public required string[] PhoneNumbers { get; init; }
}
public class Program
{
public static void Main()
{
Person person1 = new("Nancy", "Davolio") { PhoneNumbers = new string[1] };
Console.WriteLine(person1);
// output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] }
Person person2 = person1 with { FirstName = "John" };
Console.WriteLine(person2);
// output: Person { FirstName = John, LastName = Davolio, PhoneNumbers = System.String[] }
Console.WriteLine(person1 == person2); // output: False
person2 = person1 with { PhoneNumbers = new string[1] };
Console.WriteLine(person2);
// output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] }
Console.WriteLine(person1 == person2); // output: False
person2 = person1 with { };
Console.WriteLine(person1 == person2); // output: True
}
}
For more information, see Records (C# reference).
C# Language Specification
For more information, see the C# Language Specification. The language specification is the definitive source for C# syntax and usage.