Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

C# Dictionary

  1. Home
  2. C# Dictionary
  3. record (C#)

record (C#)

A type introduced in C# 9 designed specifically for holding data. It is designed as an immutable object and provides compact syntax for copy-creation with with expressions, value-based equality comparison, and deconstruction via Deconstruct.

Syntax

record TypeName(Type PropertyName, ...);

// Block-form record (allows defining additional members).
record TypeName(Type PropertyName, ...)
{
    // Additional properties and methods can be defined here.
}

// with expression: copies the record while changing selected properties.
TypeName newVar = originalVar with { PropertyName = newValue };

// record struct (value-type record; C# 10 and later).
record struct TypeName(Type PropertyName, ...);

Key Features of record

FeatureDescription
Immutable properties (init-only)Properties of a positional record are generated with an init accessor, so they cannot be changed after initialization.
Value-based equalityTwo records are considered equal with == when their property values match, not their references.
with expressionCreates a copy of the original object with only the specified properties changed, leaving the original untouched.
DeconstructA positional record automatically generates a deconstruction method, allowing its values to be unpacked into variables like a tuple.
Auto-generated ToString()Automatically returns a string containing the type name along with each property name and value.
record structA value-type record. No heap allocation is required; it is placed on the stack (C# 10 and later).

record vs. class Comparison

Itemrecord (reference type)class
Equality (==)Value-based (property contents)Reference-based (same instance)
ImmutabilityAutomatically immutable via init accessor for positional records.You must design readonly fields or properties yourself.
with expressionSupported.Not supported.
DeconstructAuto-generated for positional records.Must be defined manually.
ToString()Auto-generated (type name + properties)Default returns type name only.
InheritanceA record can only inherit from another record.A class can inherit from another class.

Sample Code

RecordBasic.cs
using System;

// The compiler automatically generates a constructor, init-only properties, Deconstruct, ToString, and ==.
record Member(string Name, string Level, int Score);

class RecordBasic {
	static void Main() {

		// Creates instances of the record.
		var member_2 = new Member("member_2", "A", 80000);
		var member_1 = new Member("member_1", "S", 999999);
		var member_3 = new Member("member_3", "A", 75000);

		// ToString() is auto-generated.
		Console.WriteLine(member_2);
		// → Member { Name = member_2, Level = A, Score = 80000 }

		// Two instances with the same property values are considered equal with ==.
		var member_2b = new Member("member_2", "A", 80000);
		Console.WriteLine(member_2 == member_2b); // True
		Console.WriteLine(member_2 == member_1); // False

		var member_2_updated = member_2 with { Score = 999999 };
		Console.WriteLine(member_2_updated);
		// → Member { Name = member_2, Level = A, Score = 999999 }

		// The original instance is unchanged (immutability).
		Console.WriteLine(member_2.Score); // 80000

		var (name, level, score) = member_3;
		Console.WriteLine($"{name} / {level} / Score: {score}");
		// → member_3 / A / Score: 75000
	}
}

This produces the following output:

dotnet script RecordBasic.cs
Member { Name = member_2, Level = A, Score = 80000 }
True
False
Member { Name = member_2, Level = A, Score = 999999 }
80000
member_3 / A / Score: 75000
RecordBlock.cs
using System;

// A block-form record. In addition to positional properties, additional members can be defined.
record Project(string Owner, string Category)
{
	// Adds a computed property (can be written in block-form records).
	public string Description
		=> $"{Owner}'s project: {Category}";

	// Custom methods can also be defined.
	public bool IsCritical()
	{
		// Treats a category containing "urgent" as a critical project.
		return Category.Contains("urgent");
	}
}

class RecordBlock {
	static void Main() {

		var projA = new Project("member_1", "review (urgent)");
		var projB = new Project("member_3", "research");
		var projC = new Project("member_6", "deploy (urgent)");

		Console.WriteLine(projA.Description);
		// → member_1's project: review (urgent)

		Console.WriteLine($"projA is critical: {projA.IsCritical()}"); // True
		Console.WriteLine($"projB is critical: {projB.IsCritical()}"); // False
		Console.WriteLine($"projC is critical: {projC.IsCritical()}"); // True

		// The with expression also works with block-form records.
		var projBUpdated = projB with { Category = "research (urgent)" };
		Console.WriteLine($"Updated: {projBUpdated.Description}");
		// → member_3's project: research (urgent)
	}
}

This produces the following output:

dotnet script RecordBlock.cs
member_1's project: review (urgent)
projA is critical: True
projB is critical: False
projC is critical: True
Updated: member_3's project: research (urgent)
RecordVsRecordStruct.cs
using System;

// record (reference type): allocated on the heap.
record TeamA(string Name, int Rank);

// record struct (value type): allocated on the stack (C# 10 and later).
// Positional properties are mutable by default (a set accessor is generated).
record struct TeamB(string Name, int Rank);

class RecordVsRecordStruct {
	static void Main() {

		var entryA1 = new TeamA("item_a", 20);
		var entryA2 = new TeamA("item_a", 20);

		// Value-based comparison (reference-type records compare by property values).
		Console.WriteLine(entryA1 == entryA2); // True

		var entryB1 = new TeamB("item_b", 16);
		var entryB2 = new TeamB("item_b", 16);

		// record struct also uses value-based comparison.
		Console.WriteLine(entryB1 == entryB2); // True

		// Properties of a record struct can be modified (unlike a reference-type record).
		entryB1.Rank = 18; // record struct is mutable by default.
		Console.WriteLine(entryB1); // TeamB { Name = item_b, Rank = 18 }

		// The with expression also works with record struct.
		var entryB3 = new TeamB("item_c", 19);
		var entryB3Modified = entryB3 with { Rank = 15 };
		Console.WriteLine(entryB3Modified); // TeamB { Name = item_c, Rank = 15 }

		// The original entryB3 is unchanged.
		Console.WriteLine(entryB3); // TeamB { Name = item_c, Rank = 19 }
	}
}

This produces the following output:

dotnet script RecordVsRecordStruct.cs
True
True
TeamB { Name = item_b, Rank = 18 }
TeamB { Name = item_c, Rank = 15 }
TeamB { Name = item_c, Rank = 19 }

Common Mistakes

Directly assigning to an init-only property

Properties of a positional record are generated with an init accessor, so attempting to change a value after initialization causes a compile error. Use a with expression to create a new instance with the desired changes.

record Member(string Name, int Score);

var entry = new Member("item_x", 9000);

// Compile error: an init-only property cannot be modified after initialization.
// entry.Score = 15000;

var entryUpdated = entry with { Score = 15000 };

Mutability difference between record and record struct

A reference-type record is init-only (immutable), but record struct properties have a set accessor by default, making them mutable. To make them immutable, declare the type as readonly record struct.

record RefRecord(string Name, int Value);
record struct MutableStruct(string Name, int Value);
readonly record struct ImmutableStruct(string Name, int Value);

var r = new RefRecord("A", 1);
// r.Value = 2; // Compile error: init-only.

var ms = new MutableStruct("B", 2);
ms.Value = 3; // record struct is mutable by default.

var ims = new ImmutableStruct("C", 3);
// ims.Value = 4; // Compile error: readonly record struct is immutable.

Notes

record is a type designed primarily for holding data. With positional syntax, the compiler automatically generates the constructor, init-only properties, Deconstruct, ToString, and equality comparison — significantly reducing boilerplate compared to an equivalent class.

The with expression is used to create a copy that differs in only select properties without modifying the original object. Directly assigning to an init-only property (e.g., member_2.Score = 999;) is a compile error. Always use a with expression when you need a modified copy.

Because record struct is a value type, it is allocated on the stack with no heap allocation. However, positional record struct properties are mutable (set accessor), which differs from the immutability of a reference-type record. To make it immutable, declare it as readonly record struct.

For type checking and safe casting, see is / as / Pattern Matching. For use with nullable types, see Nullable<T> / Nullable Types.

If you find any errors or copyright issues, please .