本書について
本書では、C# でイミュータブルデータ設計(immutable data design)を活用し、ミュータブルな状態がコード内で受け渡されることで生じる、コードの理解やデバッグの負担を減らす方法を紹介します。record、init、required、with 式、イミュータブルコレクション、パターンマッチングを使い、「オブジェクトを変更する」処理を、追跡、テスト、保守がしやすいデータ変換へと書き換えていきます。
本書が扱うのは、既存の C# プロジェクトにおける設計上の選択です。どのデータをイミュータブルに保つとよいのでしょうか。どの場面では、ミュータブルなオブジェクトを残すほうがよいのでしょうか。データが API、データベース、バックグラウンド処理、テストケースをまたいで受け渡されるとき、どのような型設計ならコードの信頼性を高められるのでしょうか。
対象読者と前提知識
本書は、C# での開発経験があり、データモデル設計、API 設計、状態管理を改善したい開発者を対象としています。
前提知識
本書を読むにあたっては、次の C# の構文や機能を理解し、実際に使ったことがある読者を想定しています。
- オブジェクト指向の基礎:クラス(class)、インターフェイス(interface)、継承、カプセル化。
- 近年の C# の構文と機能:LINQ、
async/awaitによる非同期プログラミング、null 許容参照型(nullable reference types)。
関数型プログラミング(functional programming)や高度な型理論の知識は必要ありません。また、ドメイン駆動設計(Domain-Driven Design、DDD)や特定のレイヤーアーキテクチャに詳しい必要もありません。domain model、application layer、repository などの一般的な用語を使う場合は、初出時に簡単に説明します。本書の主な用語は、用語集でも確認できます。
本書が役立つ場面
次のような経験があるなら、本書から設計や実装を見直すヒントを得られるでしょう。
- 一つのオブジェクトが複数のメソッドやサービス、各レイヤーで何度も書き換えられ、結局どこで値が変わったのか、誰が壊したのかがわからなくなる。
record、init、with、requiredなどの構文は知っているが、実際のシステム設計でどう組み合わせればよいかわからない。- Web API の request / response DTO を設計するとき、ミュータブルな class、record class、readonly struct のどれにするか迷う。
- イミュータブルなデータの利点は理解しているが、深くネストした(deeply nested)データ構造を実際にどう更新すればよいかわからない。
- ORM(Entity Framework Core など)、JSON シリアル化、UI データバインディング、性能が重要なコードなど、実際のフレームワークの制約とイミュータブル設計のバランスを取りたい。
本書の構成
本書は全 6 章で構成され、全体を通じて注文処理システムを主な例として使います。注文システムを選んだのは、状態遷移、API の入出力データ、コレクション、検証、データの永続化、状態モデリング(state modeling)といった、一般的な設計課題が含まれているためです。
- 第 1 章 ミュータブルなオブジェクトからイミュータブルなデータへ:状態を追跡する難しさを出発点に、「オブジェクトをその場で変更する」処理を「古いバージョンから新しいバージョンを作る」処理へ変える方法を紹介します。
- 第 2 章 C# の records、init、イミュータブルコレクション:イミュータブルなデータを作るための構文とコレクションを学び、
recordを使っていてもミュータブルなままになる、よくある落とし穴を確認します。 - 第 3 章 その場での変更をデータ変換に置き換える:注文処理のフローを例に、データ変換、ビジネスルールの検証、外部依存の扱い方を示し、振る舞いを型の内部に置くべきか、外部サービスに置くべきかを考えます。
- 第 4 章 API、DTO、システム境界のデータモデル:API のデータ契約や設定オブジェクトにイミュータブル設計を適用し、検証、シリアル化、DTO と domain model の間の変換を整理します。
- 第 5 章 イミュータブル設計のトレードオフと導入:適用場面とコストを評価して段階的な導入の起点を選び、AI が提案したリファクタリングが実際の問題を解決しているかを検討します。
- 第 6 章 型で不正な状態を排除する(発展編):単一の
recordとenumの組み合わせでは正しい状態を表現しにくくなったとき、状態ごとの型や共用体型(union types)の効果と導入コストを評価し、C# 14 と C# 15 の方法を比較します。
付録 A では、テキストを点字に変換するオープンソースのシステムを例に、コードがミュータブルなモデル、オブジェクト参照、既存のシリアル化形式に依存している場合に、ミュータブルな範囲を段階的に狭める方法を説明します。既存プロジェクトのリファクタリングを検討している読者の参考になるでしょう。
読み進め方
まずは第 1 章から第 5 章までを順に読み、本書の核となる内容を一通り押さえることをお勧めします。第 6 章は発展的な内容です。現在の状態モデルがすでに明確で信頼できるなら、そのまま「おわりに」へ進んでもかまいません。既存プロジェクトへの導入に取り組む際は、付録 A の事例も併せて読むとよいでしょう。
コードを読むときは、構文だけでなく、それぞれの例が示そうとしている設計意図に注目してください。イミュータブルデータ設計の目的は、すべてのプロパティを読み取り専用にすることではなく、データがいつ作られ、どう使われ、どこで状態が変わるのか、そして誰がその変更を担当するのかを明確にすることです。
実際に手を動かしたい場合は、本書のサンプルコードをダウンロードし、各章の書き換え手順に沿ってテストを実行してみてください。自分のプロジェクトで試したい箇所が思い浮かんだら、小さなデータフローを一つ選んで自分で書き換えてみるか、AI エージェントに変更を手伝ってもらうとよいでしょう。そのうえで変更前後の違いを比較し、関連するテストを実行してみてください。一度にすべてを変える必要はありません。一組の DTO、一つの value object、一つのデータフローをより信頼できるものにするだけでも、実用上のメリットが得られます。
読む端末について:コードや図表を読みやすくするため、パソコンまたはタブレットでの読書をお勧めします。電子書籍リーダーを使う場合は、画面の大きな端末を選ぶとよいでしょう。
技術環境と開発ツール
本書の演習に取り組み、サンプルコードを実行するための環境は次のとおりです。
- 基本環境(第 1 章〜第 5 章): C# 14 と .NET 10 を基準に開発しています。本書の設計原則や構文の大部分は、C# 10(.NET 6)以降でも適用できます。ただし、一部の例は新しい構文や API を使っているため、古いバージョンでは調整が必要です。たとえば、第 1 章で使う
[]コレクション式には C# 12 以降が必要です。第 1 章から第 5 章のサンプルプロジェクトは .NET 10 をターゲットフレームワークとしています。 - 発展編の環境(第 6 章): 第 6 章では C# 15 の構文(union types と closed hierarchies)を使うため、C# 15 に対応した .NET 11 SDK が必要です。6.3 節には C# 14 / .NET 10 で実行できる状態 record の階層の例もあり、継承の制限や網羅性チェックについて union との違いを説明します。
- 開発ツール: Visual Studio 2026、Visual Studio Code、JetBrains Rider、または最近の .NET に対応した IDE/エディター。
サンプルコード
本書のサンプルコードは GitHub で公開しています。
https://github.com/ippobooks/csharp-immutable-data-design-samples
表記について
用語によっては、訳すことで意味が伝わりにくくなったり、理解の負担が増えたりすることがあります。そのため本書では、DTO、domain model、value object など、必要に応じて英語表記を残しています。重要な用語は初出時に訳語と英語を併記し、その後は文脈に応じて、自然で読みやすい表現を使います。