Records

Records can be record classes or record structs.

Use When

  • You need a data model that depends on value equality
  • You need a type whose objects are immutable

Do Not Use

  • As entity types in Entity Framework Core.

Positional Syntax

Positional syntax will automatically declare properties, a constructor, and a deconstructor:

public record Person(string FirstName, string LastName);

public static void Main()
{
    Person person = new("Nancy", "Davolio");
    Console.WriteLine(person);
    // output: Person { FirstName = Nancy, LastName = Davolio }
}

Record Class

Record classes, a reference type (defaults to record class if class or struct not specified):

public record Person 
{
    public string FirstName { get; init; } = default!
    public string LastName { get; init; } = default!
};

Characteristics

  • Allocation: Heap
  • Equality: Reference
  • Semantics: Reference
  • Inheritance: Single (from another record only)
  • Mutability: Mutable, but generally treated immutable

A positional record class declares init-only properties, making them immutable.

Record Struct

Record struct, a value type:

public record struct Point 
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Z { get; set; }
}

Characteristics

  • Allocation: Stack
  • Equality: Value
  • Semantics: Value
  • Inheritance: None (sealed)
  • Mutability: Mutable, but generally treated immutable

A positional record struct declares read-write properties, making them mutable.

Readonly Record Struct

A positional readonly record struct declares init-only properties, making them immutable.

Example

public record Person(string FirstName, string LastName);

public static void Main()
{
    Person person = new("Nancy", "Davolio");
    Console.WriteLine(person);
    // output: Person { FirstName = Nancy, LastName = Davolio }
}

Equality

For record class types, two objects are equal if they refer to the same object in memory. For record struct and readonly record struct types, two objects are equal if they are of the same type and store the same values.

  • This is the same as equality for a struct type.

Adding Targets to Attributes Applied to Records

The property target applies the attribute to the compiler-generated auto-property.
The field target applies the attribute to the field.
The param target applies the attribute to a parameter.

public record Person(
    [property: JsonPropertyName("firstName")] string FirstName,
    [property: JsonPropertyName("lastName")] string LastName)
{
    public string PhoneNumber { get; init; }
}

Non-destructive Mutation

By using the with keyword, a new record instance is created with specified properties and fields modified:

person p1 = new("Super", "Man") { PhoneNumber = 5555555555 };
person p2 = p1 with { FirstName = "Bat" };

Built-in Formatting for Display

Records have a compiler-generated ToString method that displays the names and values of public properties and fields. It uses this format: record-type-name { property-name = value, property-name = value, … }

An implementation of ToString may include the sealed modifier, which prevents the compiler from synthesizing a ToString implementation for any derived records. This creates a consistent string representation throughout a hierarchy of record types.

Examples

// You can give a record a constructor:
public record class ImmutableAnimal 
{
    public string Name { get; init; }
    public string Species { get; init; }

    public ImmutableAnimal(string name, string species) 
    {
        Name = name;
        Species = species;
    }

    // And a deconstructor:
    public void Deconstruct(out string name, out string species) 
    {
        name = Name;
        species = Species;
    }
}