record(C#)
『C#』9で追加されたデータ保持専用の型です。不変オブジェクトとして設計されており、with式によるコピー生成、値ベースの等価比較、Deconstruct による分解をコンパクトな構文で実現できます。
構文
// 位置指定record(プロパティを自動生成します)。
record 型名(型 プロパティ名, ...);
// ブロック形式のrecord(追加メンバーを定義できます)。
record 型名(型 プロパティ名, ...)
{
// 追加のプロパティやメソッドを定義できます。
}
// with式でコピーしながら一部だけ変更します。
型名 新変数 = 元変数 with { プロパティ名 = 新しい値 };
// record struct(値型のrecordです。C# 10以降)。
record struct 型名(型 プロパティ名, ...);
record の主な特徴一覧
| 機能 | 概要 |
|---|---|
| 不変プロパティ(init専用) | 位置指定recordのプロパティは init アクセサで生成されるため、初期化後に変更できません。 |
| 値ベースの等価比較 | 参照ではなくプロパティの値が同じであれば == で等しいと判定されます。 |
| with式 | 元のオブジェクトはそのままに、指定したプロパティだけ変更したコピーを生成します。 |
| Deconstruct | 位置指定recordは自動的に分解メソッドが生成されるため、タプルのように変数へ展開できます。 |
| ToString() の自動生成 | 型名とプロパティ名・値を含む文字列を自動で返します。 |
| record struct | 値型のrecordです。ヒープ割り当てが不要で、スタック上に配置されます(C# 10以降)。 |
record と class の比較
| 比較項目 | record(参照型) | class |
|---|---|---|
| 等価比較(==) | 値ベース(プロパティの内容) | 参照ベース(同じインスタンスか) |
| 不変性 | 位置指定なら init アクセサで自動的に不変 | 自分で readonly やプロパティを設計する必要があります。 |
| with式 | 使えます。 | 使えません。 |
| Deconstruct | 位置指定recordなら自動生成されます。 | 自分で定義する必要があります。 |
| ToString() | 自動生成(型名 + プロパティ) | 既定は型名のみ |
| 継承 | recordからrecordのみ継承できます。 | classからclassを継承できます。 |
サンプルコード
RecordBasic.cs
using System;
// 位置指定recordを定義します。
// コンパイラがコンストラクタ・init専用プロパティ・Deconstruct・ToString・==を自動生成します。
record Member(string Name, string Level, int Score);
class RecordBasic {
static void Main() {
// 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() は自動生成されます。
Console.WriteLine(member_2);
// → Member { Name = member_2, Level = A, Score = 80000 }
// --- 値ベースの等価比較 ---
// 同じプロパティ値であれば別インスタンスでも == で等しいと判定されます。
var member_2b = new Member("member_2", "A", 80000);
Console.WriteLine(member_2 == member_2b); // True
Console.WriteLine(member_2 == member_1); // False
// --- with式: コピーしながら一部を変更します ---
var member_2_updated = member_2 with { Score = 999999 };
Console.WriteLine(member_2_updated);
// → Member { Name = member_2, Level = A, Score = 999999 }
// 元のインスタンスは変わりません(不変性)。
Console.WriteLine(member_2.Score); // 80000
// --- Deconstruct: タプルのように変数へ展開します ---
var (name, level, score) = member_3;
Console.WriteLine($"{name} / {level} / スコア: {score}");
// → member_3 / A / スコア: 75000
}
}
コンパイルして実行すると次のようになります。
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 / スコア: 75000
RecordBlock.cs
using System;
// ブロック形式のrecordです。位置指定プロパティに加え、追加のメンバーを定義できます。
record Project(string Owner, string Category)
{
// 計算プロパティを追加します(ブロック形式の場合に記述できます)。
public string Description
=> $"{Owner} のプロジェクト:{Category}";
// 独自メソッドも定義できます。
public bool IsCritical()
{
// カテゴリに「urgent」を含むプロジェクトを重要と見なします。
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 のプロジェクト:review (urgent)
Console.WriteLine($"projAは重要: {projA.IsCritical()}"); // True
Console.WriteLine($"projBは重要: {projB.IsCritical()}"); // False
Console.WriteLine($"projCは重要: {projC.IsCritical()}"); // True
// with式はブロック形式のrecordでも使えます。
var projBUpdated = projB with { Category = "research (urgent)" };
Console.WriteLine($"更新後: {projBUpdated.Description}");
// → member_3 のプロジェクト:research (urgent)
}
}
コンパイルして実行すると次のようになります。
dotnet script RecordBlock.cs member_1 のプロジェクト:review (urgent) projAは重要: True projBは重要: False projCは重要: True 更新後: member_3 のプロジェクト:research (urgent)
RecordVsRecordStruct.cs
using System;
// record(参照型)です。ヒープに確保されます。
record TeamA(string Name, int Rank);
// record struct(値型)です。スタックに確保されます(C# 10以降)。
// 位置指定プロパティはデフォルトで可変です(setアクセサが生成されます)。
record struct TeamB(string Name, int Rank);
class RecordVsRecordStruct {
static void Main() {
// --- record(参照型)---
var entryA1 = new TeamA("item_a", 20);
var entryA2 = new TeamA("item_a", 20);
// 値ベース比較(参照型のrecordはプロパティ値で比較します)。
Console.WriteLine(entryA1 == entryA2); // True
// --- record struct(値型)---
var entryB1 = new TeamB("item_b", 16);
var entryB2 = new TeamB("item_b", 16);
// 値型のrecord structも値ベース比較です。
Console.WriteLine(entryB1 == entryB2); // True
// record structのプロパティは変更できます(参照型recordと異なる点)。
entryB1.Rank = 18;
Console.WriteLine(entryB1); // TeamB { Name = item_b, Rank = 18 }
// 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 }
// 元の entryB3 は変わりません。
Console.WriteLine(entryB3); // TeamB { Name = item_c, Rank = 19 }
}
}
コンパイルして実行すると次のようになります。
dotnet script RecordVsRecordStruct.cs
True
True
TeamB { Name = item_b, Rank = 18 }
TeamB { Name = item_c, Rank = 15 }
TeamB { Name = item_c, Rank = 19 }
よくあるミス
init 専用プロパティに直接代入する
位置指定 record のプロパティは init アクセサで生成されるため、初期化後に値を変更しようとするとコンパイルエラーになります。変更が必要な場合は with 式で新しいインスタンスを作成してください。
record Member(string Name, int Score);
var entry = new Member("item_x", 9000);
// コンパイルエラー: init 専用プロパティは初期化後に変更できません。
// entry.Score = 15000;
var entryUpdated = entry with { Score = 15000 };
record と record struct の可変性の違い
参照型の record は init 専用(不変)ですが、record struct のプロパティはデフォルトで set アクセサが生成されるため可変です。不変にしたい場合は 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; // コンパイルエラー: init 専用です。
var ms = new MutableStruct("B", 2);
ms.Value = 3;
var ims = new ImmutableStruct("C", 3);
// ims.Value = 4; // コンパイルエラー: readonly record struct は変更できません。
概要
『record』はデータ保持を主目的とする型です。位置指定構文でプロパティ・コンストラクタ・Deconstruct・ToString・等価比較のすべてが自動生成されるため、同等の class と比べて大幅にコード量を削減できます。
with式は元のオブジェクトを変更せずに「一部だけ違うコピー」を作るためのものです。init専用プロパティへの直接代入(member_2.Score = 999;)はコンパイルエラーになります。変更が必要な場合は必ずwith式を使ってください。
record struct は値型なので、スタックに確保されてヒープ割り当てが発生しません。ただし位置指定の record struct はプロパティが可変(setアクセサ)であり、参照型の record の不変性とは異なります。不変にしたい場合は readonly record struct と宣言します。
型の判定と安全なキャストは『is / as / パターンマッチ』を、null許容型との組み合わせは『Nullable<T> / null許容型』を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。