はじめに
『C#クックブック ―プロフェッショナル開発者のためのモダンレシピ』を読みました。
C#の文法は分かるけれど、実務でどう組み立てればよいのか。本書はその問いに、90個の小さなレシピで答えていく一冊です。
この記事では、設計・アーキテクチャに関心のあるエンジニアの視点で、本書の構成、刺さったレシピ、読むときの注意点をまとめます。
本記事は書評であり、書籍の内容を転載するものではありません。コードは理解のために簡略化して書き直しています。詳細は書籍とサンプルコードをご確認ください。
書籍情報
| 項目 | 内容 |
|---|---|
| 書名 | C#クックブック ―プロフェッショナル開発者のためのモダンレシピ |
| 著者 | Joe Mayo |
| 訳者 | 鈴木 幸敏 |
| 出版社 | オライリー・ジャパン |
| 初版 | 2023年2月1日 |
| ISBN | 978-4-8144-0018-8 |
| 基準バージョン | C# 9 |
本書のスタイル
「課題 → 解決策 → 解説 → 関連項目」で統一
すべてのレシピが同じ型で書かれています。
- 課題:現場で起きがちな困りごと
- 解決策:動くコード
- 解説:なぜそうするのか、何が代償か
- 関連項目:他のレシピへの参照
コードを貼って終わりではなく、解説でトレードオフまで踏み込んでいます。
著者自身が「本書の通りにすること、という提案はしない」と述べており、状況に応じて発展させる前提で書かれています。
章ごとに共通の題材を使う
デプロイ処理や請求書(1〜3章)、チェックアウト処理(6章)、ホテル予約(8章)、住所データ(9章)など、章内で題材が固定されています。
レシピをまたいで読んでも、文脈を取り戻しやすい作りです。
全体構成
| 章 | テーマ | 一言でいうと |
|---|---|---|
| 1章 | 型とアプリケーションの構築 | 設計の土台(SoC、IoC、各種パターン) |
| 2章 | アルゴリズムのコーディング | 性能・保守性・考え方の切り替え |
| 3章 | 品質の維持 | テスト、null安全、例外、計測 |
| 4章 | LINQによるデータ取得 | LINQ to Objectsによる宣言的処理 |
| 5章 | 動的オブジェクトとリフレクション | 実行時に型を扱う技術、Python連携 |
| 6章 | 非同期プログラミング | async/awaitの正しい使いどころ |
| 7章 | データ処理 | 機密情報、JSON、XML |
| 8章 | パターンマッチ | 条件分岐を宣言的に書く |
| 9章 | 近年におけるC#の動向 | C# 9と不変性 |
1〜3章は「C#開発者は何をする必要があるのか?」という問いに沿った流れで、4章以降は技術テーマ別です。
著者のコーディング順(型を作る → ロジックを足す → 品質を守る)なので、前半は設計の読み物としても通読できます。
1章 型とアプリケーションの構築
設計に関心がある方には、この章がいちばんの読みどころです。
- 1.1 オブジェクトの終了期間の管理(破棄パターン)
- 1.2 明示的な依存の削除(DI / IoC)
- 1.3 / 1.4 オブジェクト生成をクラス / メソッドに任せる(ファクトリ)
- 1.5 アプリケーション層の設計
- 1.6 メソッドから複数の値を返す(タプル)
- 1.7 レガシーなクラスを強く型付けされたクラスに切り替える
- 1.8 クラスを独自のインターフェイスに対応させる(アダプター)
- 1.9 独自の例外を設計する
- 1.10 複雑な設定でオブジェクトを構成する(ビルダー)
1.1 破棄パターンが「最初のレシピ」である意味
ファイルハンドルのようなリソースを握ったまま放置すると、原因の特定が難しい障害になります。
本書がこれを最初に置いているのは、品質の土台だからだと感じました。
解説は次の点が整理されていて、「なんとなく書いていた定型コード」の意味が分かります。
-
disposedフラグは二重破棄を防ぐ -
disposing引数は「明示的なDisposeか、ファイナライザからか」を表す - アンマネージリソースを直接持たないなら、ファイナライザは実装しない
- 派生クラスでの実装ミスに備え、
Dispose()ではGC.SuppressFinalizeを呼んでおく
1.2 DIコンテナは「依存の置き場所を1箇所にする」道具
密結合の問題を、Microsoft.Extensions.DependencyInjectionで解決する流れが示されます。依存する型を1箇所で登録しておき、コンテナに生成を任せる形です。
AddTransient / AddSingleton / AddScopedといったライフタイムの違いにも触れています。
DIの流派については「議論が続いているので深入りしない」と割り切っており、実務書らしい判断だと思いました。
1.3〜1.4 ファクトリは「DIしづらい相手」への逃げ道
インターフェイスを持たないサードパーティ製クラスは、そのままではDIしづらいものです。
そこでファクトリで生成の責務を切り出します(1.3)。
1.4では抽象基底クラスで生成メソッドを強制し、プラグイン型のフレームワークを作ります。
拡張者の作業は「プラグインを実装する → 生成クラスを派生する → 一覧に追加する」の3つだけで、呼び出し側は具体的なプラグインを知りません。
1.5 階層化アーキテクチャは「厳格な規則」ではない
いちばん印象に残った解説です。
- 1つのプロジェクトに詰め込む「大きな泥だんご」は、プロトタイプなら速いが長期では致命的
- UI・ビジネスロジック・データの3層に分けると、コードの置き場所が明確になる
- ただし「必ず隣の層を経由しなければならない」というのは誤解で、現実的ではない
規模に応じた構成の違いも示されています。
| 規模 | 構成の目安 |
|---|---|
| 小さなユーティリティ | 1プロジェクトに各層を同居させてもよい |
| 中規模 | データアクセス層を独立させ、再利用できるようにする |
| 大規模 | 各層を独立プロジェクトにし、結合度を厳しく管理する |
分離すること自体が目的ではなく、保守性を高めるための手段だと繰り返し述べられています。
1.6〜1.10 モダンC#の書き味
-
1.6:
out引数や専用型の代わりに、タプルとDeconstructで複数値を返す -
1.7:
objectベースのレガシーコレクションをジェネリックにリファクタリングする - 1.8:アダプターで、インターフェイスの違うライブラリを同じ扱いにそろえる
-
1.9:独自例外には原因を表すプロパティを持たせ、
ToStringもオーバーライドする - 1.10:オプションが多い型は、コンストラクタを増やさずビルダーで組み立てる
2章 アルゴリズムのコーディング
「性能」「メンテナンス性」「マインドセット」の3つの観点で整理された章です。
- 2.1 文字列の効率的な処理
- 2.2 インスタンスのクリーンアップを単純化する(
using宣言) - 2.3 ロジックをローカルに閉じておく(ローカル関数)
- 2.4 複数のクラスを同じように呼び出す(ストラテジー)
- 2.5 型の等価性をチェックする
- 2.6 階層的データを処理する(再帰)
- 2.7 Unix時間との相互変換
- 2.8 頻繁にリクエストされるデータをキャッシュする
- 2.9 型のインスタンス化を遅延させる(
Lazy<T>) - 2.10 データフィールドを解析する(正規表現)
2.1 ループ内の文字列結合はStringBuilderへ
同じ式の中での結合はコンパイラが最適化しますが、ループ中の結合は別です。
経験則として「4回以上結合するならStringBuilder」という目安が示されます。
さらに、SQLを文字列結合で組み立てると、性能だけでなくSQLインジェクションの原因になるとも書かれています。
その場合はStringBuilderではなく、パラメータ化に対応したデータライブラリやLINQを使うべきだ、という整理です。
2.3 ローカル関数は「意図に名前を付ける」手段
1箇所でしか使わない複雑なロジックを別メソッドにすると、クラスのメンバーが散らかります。
ローカル関数なら、名前を付けつつスコープも閉じられます。
2.4 ストラテジーでifとswitchを減らす
IInvoiceを実装したクラスを並べ、呼び出し側は具体型を意識しない構成です。
さりげなく書かれた設計のコツが印象的でした。戻り値をList<T>ではなくIEnumerable<T>にしておくと、将来、内部のコレクション実装を変えても呼び出し側を壊さずに済みます。
2.5 等価性は「何を比較するか」の設計
IEquatable<T>を実装する際は、Equals、GetHashCode、==、!=を一貫して実装し、比較対象を業務上の同一性を表す項目に絞ります。
C# 9のrecordは値等価性を自動実装しますが、タイムスタンプやGUIDのように毎回違う値を含む型では、意図せず「絶対に等しくならない」状態になりうる、という注意も書かれています。
2.8〜2.9 キャッシュと遅延初期化には「条件」がある
-
2.8:件数が少なく変更頻度も低いデータは、メモリにキャッシュする。静的フィールドはプロセスの再利用などで消えうるため、
nullなら再取得する書き方にしておく -
2.9:起動コストの高いオブジェクトは
Lazy<T>で遅延させる
「キャッシュしてよいデータの条件」が明文化されているので、判断基準として使えます。
3章 品質の維持
主なレシピ:ユニットテスト、インターフェイスのバージョン管理、引数検証、null参照からの保護、例外の再スロー、信頼性の高いネットワーク通信、パフォーマンス計測など(全10レシピ)。
章の導入で、著者は次の点を強調しています。
- 可読性の高いコードはメンテナンス性が高く、ライフサイクルコストを下げる
- ユニットテストは広く知られているが、いまだに書かれないことが多い
- 特にnull許容参照型(3.4)は必ず確認してほしい
1章のDIが「テストしやすさ」につながることが、この章で回収されます。
3.10の計測は、2.1の文字列結合を実測で確かめる流れになっており、章をまたいだ構成も気が利いています。
4章 LINQによるデータ取得
LINQを「データベース用の技術」ではなく、メモリ上のデータを宣言的に処理する道具(LINQ to Objects)として扱う章です。
主なレシピ:形状変換、結合・外部結合、グループ化、クエリの段階的構築、重複排除、集合操作、式ツリー、PLINQなど(全10レシピ)。
設計の観点で注目したのは4.5と4.9です。
- 4.5:LINQの遅延実行を使い、検索条件に応じてクエリを段階的に組み立てる
-
4.9:式ツリーで
where句を動的に生成する
どちらも「検索フォームの条件が可変」という、業務アプリで頻出する要件への答えです。
文字列結合でクエリを作る危うさ(2.1)を、LINQ側から解消する流れで読むとつながりが見えます。
5章 動的オブジェクトとリフレクション
主なレシピ:属性の読み取り、メンバー操作、dynamicへの書き換え、Office連携、動的な型、PythonとC#の相互呼び出しなど(全10レシピ)。
題材は「クラスの属性を読み取ってレポートを動的に生成する」アプリケーションです。
ユニットテストフレームワークが属性とリフレクションでテストを探すのと同じ原理、という説明が分かりやすいです。
5.5では、冗長になりがちなリフレクションをdynamicで単純化します。
強く型付けされたコードを基本としつつ、COM相互運用や動的言語との連携ではdynamicが有効、という使い分けが整理されています。
6章 非同期プログラミング
主なレシピ:非同期のMain、ValueTask、非同期反復子、安全な非同期ライブラリ、進捗更新、並列待機、キャンセル、非同期リソースの破棄など(全10レシピ)。
導入では、TPLとasync/awaitの役割の違いが整理されています。
- TPL:CPU負荷の高い処理をマルチスレッドで実行する
- async/await:ファイル、DB、REST APIなど、プロセス外とのやり取りでスレッドを占有しない
題材はチェックアウト処理(住所検証 → 与信 → カート取得 → 確定)です。
基本形は同期コードとほぼ同じ見た目ですが、本章の価値は、その先の「注意すべき点」(キャンセル、進捗、並列待機、非同期での破棄)をひと通り扱っているところにあります。
7章 データ処理
主なレシピ:パスワードのハッシュ化、暗号化、機密情報の隠蔽、JSON・XMLの生成と処理、URLエンコード、DateTimeの柔軟な読み取りなど(全10レシピ)。
翻訳版ならではの配慮として、.NET 7以降で非推奨となった暗号関連クラスは、新しいAPIに書き換えたうえで訳注が付けられています。
7.1はソルト付きのMD5/SHA-256ハッシュが題材ですが、実運用のパスワード保存では、より計算コストの高い方式(PBKDF2、bcrypt、Argon2など)も検討すべきです。レシピは仕組みを理解する入口として読み、採用時には最新のガイドラインを確認してください。
JSONの3レシピは、自分で制御できないデータ(第三者が作ったJSON)をどう扱うかに焦点が当たっています。
生産側と消費側の両方を握っていれば簡単ですが、そうでない場合はカスタマイズが必要になる、という現実的な視点です。
8章 パターンマッチ
主なレシピ:is/asの置き換え、例外フィルター、switch式、プロパティ・タプル・位置・範囲・型による判定、論理パターンなど(全10レシピ)。
題材はホテル予約システムで、会員ランクと滞在実績に応じたルールをパターンで表現します。
先頭の8.1が、C# 1.0から存在したis / asのリファクタリングから始まるのが良い構成です。
手続き的な型判定から徐々に宣言的な書き方へ進むので、「なぜパターンマッチが入ったのか」が自然に理解できます。
9章 近年におけるC#の動向
主なレシピ:トップレベルステートメント、init、record、with式、共変戻り値型、配列のスライシング、モジュール初期化など(全10レシピ)。
この章のテーマは**不変性(immutability)**です。
マルチスレッドでの安全性や、「引数で渡したオブジェクトが書き換えられない」という保証に効くため、設計面でも重要な考え方です。
題材の住所データは、「識別子を持たない値」として扱うべきだと整理されています。
ID付きのエンティティと、住所のような値を区別する、ドメイン設計の入口にもなっています。
訳者の補足によると、内容はC# 9時点ですが、その後のC# 10・11でも通用するとのことです。
設計の観点で刺さったポイント
-
関心の分離は手段であって目的ではない(1.5)
層の規則を守ること自体を目的にすると、本末転倒になる。 -
生成の責務は呼び出し側から切り離す(1.2〜1.4)
DI、ファクトリ、ファクトリメソッドは、同じ問題への粒度違いの解答として読める。 -
戻り値の型は将来の変更に耐えるように選ぶ(2.4)
List<T>ではなくIEnumerable<T>を返すという、小さいが効く判断。 -
キャッシュや遅延初期化には使ってよい条件がある(2.8、2.9)
データ量、変更頻度、プロセスの再利用まで含めて考える。 -
不変性を型で表現する(9.3〜9.6)
recordやinitを、書き方ではなく設計の話として捉えられる。
読むうえでの注意点
- 基準はC# 9です。 その後に加わった言語機能は扱っていないため、最新機能は公式ドキュメントと併読するのがおすすめです
- レシピは正解ではなく出発点です。 著者がトレードオフを前提に書いているとおり、状況に合わせて発展させる使い方が合っています。特に7章のセキュリティ関連は、採用時に最新の推奨事項を確認してください
- 辞書的に拾い読みできます。 90個のレシピは独立していますが、1〜3章は順に読むと設計の考え方がつながるので、前半だけは通読をおすすめします
こんな人におすすめ
- C#の文法は分かるが、実務での設計の型を身につけたい方
- DI、ファクトリ、ビルダー、アダプターなどのパターンを、動くコードで確認したい方
- レガシーなC#コードをモダンな書き方にリファクタリングしたい方
- 非同期、LINQ、パターンマッチを、題材付きで整理し直したい方
C#そのものが初めての方は、先に入門書で基本文法を固めたほうが読みやすいと思います。本書は「基本的なC#の文法を習得済みであること」を前提にしています。
まとめ
『C#クックブック』は、モダンC#の機能を、設計判断とセットで学べる実践的なレシピ集です。
- 課題・解決策・解説・関連項目の統一された型で、拾い読みしやすい
- 破棄パターンから階層化アーキテクチャまで、設計の「理由」まで説明してくれる
- LINQ、非同期、パターンマッチ、不変性と、現代的なC#の主要トピックを一通りカバーしている
サンプルコードは著者のGitHubリポジトリで公開されています。読みながら手元で動かすと、理解がぐっと深まります。
最後まで読んでいただき、ありがとうございました。
同じ本を読んだ方は、刺さったレシピをぜひコメントで教えてください。