【C#】record型を活用して堅牢性と等価判定を強化する

C# 9.0で導入されたrecord型は、データのまとまりを表現することに特化した参照型です。

従来のclassを使用する場合、データの等価性判定(値がすべて同じなら「等しい」とみなすこと)や、不変性(Immutability)を担保するためには、多くのボイラープレートコードを記述する必要がありました。record型を使用することで、簡潔な構文で「値としてのセマンティクス」を持つオブジェクトを定義できます。

ここでは、商品管理システムを題材に、record型の基本的な定義方法、継承、そしてクラス型とは異なる等価判定の挙動について解説します。


目次

record型の特徴とメリット

record型を採用する主なメリットは以下の通りです。

  1. 値ベースの等価性: インスタンスの参照(メモリアドレス)ではなく、プロパティの全値が一致しているかで等価性を判定します。
  2. 不変性(Immutability): プライマリコンストラクタを使用する場合、プロパティはデフォルトで初期化のみ可能な(init-only)状態となります。
  3. 簡潔な記述: コンストラクタ、分解(Deconstruct)、ToStringEqualsGetHashCodeなどが自動生成されます。

実践的なコード例:商品データの定義と等価比較

以下のコードは、基本となる「商品」レコードと、それを継承した「書籍」レコードを定義し、それぞれの比較結果を確認する例です。

using System;

namespace ProductManagement
{
    // 基本となる商品レコード
    // プライマリコンストラクタを使ってプロパティを宣言します。
    // これにより、Id と Name は自動的に init-only プロパティとなります。
    public record Product(int Id, string Name);

    // Productを継承した書籍レコード
    public record Book(int Id, string Name, string Author) : Product(Id, Name);

    class Program
    {
        static void Main()
        {
            // 1. 同じ値を持つ別のインスタンスを作成
            var product1 = new Product(1001, "高性能マウス");
            var product2 = new Product(1001, "高性能マウス");

            // 2. 継承したレコードのインスタンスを作成
            var book1 = new Book(2001, "C#入門", "山田太郎");
            
            // 参照のコピー(同じインスタンスを指す)
            var productReference = product1;

            // --- 検証結果の出力 ---

            // 自動生成されたToString()の確認
            // クラス型と異なり、プロパティの値が出力されます。
            Console.WriteLine("--- ToString() の結果 ---");
            Console.WriteLine($"product1: {product1}");
            Console.WriteLine($"book1   : {book1}");
            Console.WriteLine();

            Console.WriteLine("--- 等価性の判定 ---");

            // A. 値ベースの等価判定 (== 演算子)
            // 別のインスタンスであっても、プロパティの値がすべて同じであれば True となります。
            bool isValueEqual = (product1 == product2);
            Console.WriteLine($"product1 == product2 : {isValueEqual}");

            // B. 参照ベースの等価判定 (Object.ReferenceEquals)
            // 値が同じでも、メモリ上のインスタンスとしては別物なので False となります。
            bool isReferenceEqual = Object.ReferenceEquals(product1, product2);
            Console.WriteLine($"ReferenceEquals(p1, p2) : {isReferenceEqual}");

            // C. 参照が同じ場合の判定
            Console.WriteLine($"product1 == productReference : {product1 == productReference}");
            Console.WriteLine($"ReferenceEquals(p1, pRef) : {Object.ReferenceEquals(product1, productReference)}");

            // D. 型が異なる場合の判定
            // プロパティの一部が一致していても、型が異なれば False となります。
            // (注: コンパイル警告が出る場合がありますが、比較自体は可能です)
            Console.WriteLine($"product1 != book1 : {product1 != book1}"); // Trueになる(等しくない)

            Console.WriteLine();
            Console.WriteLine("--- ハッシュコードの確認 ---");
            // 値が同じであれば、GetHashCodeの結果も同じになります。
            Console.WriteLine($"product1 Hash: {product1.GetHashCode()}");
            Console.WriteLine($"product2 Hash: {product2.GetHashCode()}");
        }
    }
}

実行結果

--- ToString() の結果 ---
product1: Product { Id = 1001, Name = 高性能マウス }
book1   : Book { Id = 2001, Name = C#入門, Author = 山田太郎 }

--- 等価性の判定 ---
product1 == product2 : True
ReferenceEquals(p1, p2) : False
product1 == productReference : True
ReferenceEquals(p1, pRef) : True
product1 != book1 : True

--- ハッシュコードの確認 ---
product1 Hash: 1860543227
product2 Hash: 1860543227

技術的なポイント

1. 参照型でありながら値として振る舞う

recordは内部的にはclassとしてコンパイルされます(record structと明示しない限り)。しかし、コンパイラが自動的にIEquatable<T>の実装や==演算子のオーバーロードを追加するため、開発者は「値オブジェクト(Value Object)」として自然に扱うことができます。

2. インスタンスの識別

通常のクラスでは、IDなどのプロパティが同じでもインスタンスが異なれば「別物」と扱われます。しかし、上記の実行結果にある通り、record型ではproduct1 == product2Trueになります。これにより、DTO(Data Transfer Object)やAPIレスポンスの比較などが非常に容易になります。

3. ハッシュコードの自動生成

GetHashCodeメソッドもプロパティの値に基づいて計算されるようオーバーライドされます。そのため、HashSet<T>Dictionary<TKey, TValue>のキーとしてrecord型を使用した場合、値が同じであれば同じキーとして正しく認識されます。

まとめ

record型を使用することで、データの入れ物としてのクラス定義を簡略化できるだけでなく、値の等価性に基づいた堅牢な設計が可能になります。ドメインモデルの値オブジェクトや、不変性が求められるデータ構造において、積極的に採用すべき機能です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次