値の等価性と分解
recordの構造的等価性を理解し、Equals/GetHashCodeをオーバーライドして、分解パターンを使います。
「値の等価性と分解」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。
レコードの等価性を詳しく見る
レコードは、すべてのプロパティを構造的に比較する Equals、GetHashCode、および ==/!= 演算子を自動的に生成します。この仕組みを理解すると、レコードを正しく使用し、必要に応じてオーバーライドできるようになります。
自動生成される等価性の仕組み
コンパイラーは、各プロパティを EqualityComparer<T>.Default で比較するコードを生成します。参照型のプロパティでは、それぞれ独自の Equals が使用され、参照等価性が使われるわけではありません。
public record Address(string Street, string City);
public record Person(string Name, Address HomeAddress);
var a = new Person("Alice", new Address("123 Main", "NYC"));
var b = new Person("Alice", new Address("123 Main", "NYC"));
Console.WriteLine(a == b); // True — deep structural equality
// Equality checks:
// Name: "Alice" == "Alice" ✓
// HomeAddress: compares Address properties recursively ✓生成された等価性のオーバーライド
カスタムの等価性ロジックが必要な場合は、レコードの Equals と GetHashCode をオーバーライドできます。たとえば、一部のプロパティだけを比較できます。
public record Customer(int Id, string Name, string Email)
{
// Consider customers equal if they have the same ID,
// regardless of name or email changes
public virtual bool Equals(Customer? other)
=> other is not null && Id == other.Id;
public override int GetHashCode() => Id.GetHashCode();
}コレクション内のレコード
レコードは構造的等価性を持つ IEquatable<T> を実装しているため、ディクショナリのキーや HashSets で正しく動作し、LINQ の Distinct や GroupBy でも利用できます。
public record Tag(string Name, string Color);
var tags = new List<Tag>
{
new("C#", "blue"), new("C#", "blue"), new("dotnet", "purple")
};
var unique = tags.Distinct().ToList(); // [Tag(C#, blue), Tag(dotnet, purple)]
var set = new HashSet<Tag>(tags);
set.Contains(new Tag("C#", "blue")); // true分解の基礎
位置指定レコードは、各位置指定プロパティに対応する out パラメーターを持つ Deconstruct メソッドを自動的に生成します。これにより、タプルのようなアンパックが可能になります。
public record Point(double X, double Y);
public record RGB(byte R, byte G, byte B);
var p = new Point(3.0, 4.0);
var (x, y) = p; // deconstruct
Console.WriteLine($"{x}, {y}");
var color = new RGB(255, 128, 0);
var (r, g, b) = color;
Console.WriteLine($"R={r}, G={g}, B={b}");パターンマッチングでの分解
switch 式でレコードを分解すると、強力な位置パターンマッチングを利用できます。
public record Point(int X, int Y);
string Classify(Point p) => p switch
{
(0, 0) => "Origin",
(int x, 0) => $"X-axis at {x}",
(0, int y) => $"Y-axis at {y}",
(int x, int y) when x == y => $"Diagonal at {x}",
(int x, int y) => $"Point ({x}, {y})"
};
Console.WriteLine(Classify(new Point(0, 0))); // Origin
Console.WriteLine(Classify(new Point(3, 3))); // Diagonal at 3カスタム Deconstruct メソッド
非位置指定レコード(および任意のクラス)には、Deconstruct メソッドを手動で追加できます。拡張メソッドを使って既存の型に分解機能を追加することもできます。
public class Temperature
{
public double Celsius { get; }
public Temperature(double c) => Celsius = c;
// Manual Deconstruct
public void Deconstruct(out double celsius, out double fahrenheit)
{
celsius = Celsius;
fahrenheit = Celsius * 9 / 5 + 32;
}
}
var t = new Temperature(100);
var (c, f) = t;
Console.WriteLine($"{c}°C = {f}°F"); // 100°C = 212°F分解の一部を無視する
分解した値のうち不要な部分を無視するには、破棄(_)を使用します。タプルの破棄と同様です。
public record Order(int Id, string Customer, decimal Total, DateTime Date);
var order = new Order(42, "Alice", 99.99m, DateTime.UtcNow);
// Only want Id and Total
var (id, _, total, _) = order;
Console.WriteLine($"Order {id}: ${total}");
// In switch: ignore some fields
string Classify(Order o) => o switch
{
(_, _, > 1000, _) => "High value",
(_, _, > 100, _) => "Medium value",
_ => "Low value"
};プロパティパターンと位置パターン
レコードは、位置による位置パターンと、名前によるプロパティパターンの両方をサポートします。プロパティが多いレコードでは、プロパティパターンのほうが読みやすくなります。
public record Order(int Id, string Status, decimal Total);
// Positional pattern:
string DescribePos(Order o) => o switch
{
(_, "Shipped", > 100) => "High-value shipped",
_ => "Other"
};
// Property pattern (more readable):
string DescribeProp(Order o) => o switch
{
{ Status: "Shipped", Total: > 100 } => "High-value shipped",
{ Status: "Cancelled" } => "Cancelled",
_ => "Other"
};実践例: Result 型パターン
レコードと分解を使用した判別共用体スタイルの Result 型です。エラー処理に対する関数型のアプローチです。
public abstract record Result<T>;
public record Success<T>(T Value) : Result<T>;
public record Failure<T>(string Error) : Result<T>;
Result<Order> PlaceOrder(CreateOrderRequest req)
{
if (req.Quantity <= 0)
return new Failure<Order>("Quantity must be positive");
var order = CreateOrder(req);
return new Success<Order>(order);
}
// Consuming:
var result = PlaceOrder(request);
switch (result)
{
case Success<Order>(var order):
return Results.Created($"/orders/{order.Id}", order);
case Failure<Order>(var error):
return Results.BadRequest(error);
}クイックチェック
分解式で破棄(_)を使用すると、何が起こりますか。
まとめ: 値による等価性と分解
重要なポイント:
- 自動生成される等価性比較では、すべてのプロパティを構造的かつ再帰的に比較します
- 独自の同一性ロジックが必要な場合は、レコードの Equals/GetHashCode をオーバーライドします
- レコードは HashSet の要素やディクショナリのキーとして正しく機能します
- 位置指定レコードでは Deconstruct が自動生成されます — var (a, b) = record を使用します
- 破棄 (_) を使うと、分解時に不要な位置指定の値を無視できます
- 複雑なレコードでは、位置指定パターンよりプロパティパターン ({Status: "X"}) のほうが読みやすくなります
よくある質問
「値の等価性と分解」レッスンは無料ですか?
はい。「値の等価性と分解」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。
「値の等価性と分解」で何を学びますか?
recordの構造的等価性を理解し、Equals/GetHashCodeをオーバーライドして、分解パターンを使います。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C# Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「値の等価性と分解」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC# Academyレッスンでコードを書いて実行できますか?
はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。