はじめに
『プログラミングC# 第8版』(Ian Griffiths 著)を通読しました。設計やアーキテクチャに関心のあるエンジニア向けに、「この本は何を教えてくれるのか」「どこが設計の勘所として効いてくるのか」を中心にまとめます。
結論から言うと、本書は 「C#の文法書」ではなく「C#とCLRをセットで理解するための本」 です。構文の網羅よりも、「なぜその機能がその形をしているのか」の説明に紙幅が割かれています。
書籍情報
| 項目 | 内容 |
|---|---|
| 書名 | プログラミングC# 第8版 |
| 著者 | Ian Griffiths |
| 訳者 | 木下 哲也 |
| 技術監修 | 鈴木 幸敏 |
| 出版社 | オライリー・ジャパン |
| 対象バージョン | C# 8.0 / .NET Core 3.0 前後 |
| 想定読者 | 他の言語に習熟した中級〜上級の開発者 |
原書の序文によると、以前の版で扱っていたクラスやポリモーフィズムといった一般概念の解説は省かれています。「C#でそれをどう表現するか」にフォーカスした構成です。
全体構成
本書は全18章です。大きく分けると次のように読めます。
| 区分 | 章 | 内容 |
|---|---|---|
| 言語の土台 | 1〜2章 | プロジェクト構造、変数、式、データ型 |
| 型システム | 3〜6章 | 型、ジェネリック、コレクション、継承 |
| 実行モデル | 7〜8章 | オブジェクトの生存期間、例外 |
| 抽象化の道具 | 9〜11章 | デリゲート・ラムダ・イベント、LINQ、Rx |
| ランタイムとの接点 | 12〜14章 | アセンブリ、リフレクション、属性 |
| 実行効率と並行性 | 15〜18章 | ストリーム、マルチスレッド、async/await、メモリ効率 |
前半が「言語」、後半が「ランタイムとの付き合い方」という流れで、後ろに進むほど設計判断に近い話題が増えていきます。
設計の視点で刺さったポイント
1. 「専用より汎用」という言語設計の哲学
1章で語られるC#の設計思想が、本書全体の背骨になっています。
C#は特定用途の専用機能ではなく、汎用の機能を積み上げる方針をとっています。代表例がLINQです。データアクセスを言語に統合するために、次のような汎用機能が個別に追加されました。
- ラムダ式などの関数型スタイルの構文
- 継承に頼らず既存の型へメンバを足す拡張メソッド
- 匿名型
- 式の構造をオブジェクトとして取得する仕組み(式ツリー)
- クエリ構文
データベース専用の機能を足したのではなく、どれも単体で他の場面に使える部品を組み合わせてLINQを成立させています。API設計でも「特定ユースケース向けの機能」ではなく「組み合わせ可能な部品」を作る、という示唆として読めます。
埋め込みXMLがプロトタイプまで作られながら採用されなかった、という逸話も印象的でした。専用機能は技術の流行が変わると古びる、という教訓です。
2. CLRと一体で設計された言語である
C#はCLR(共通言語ランタイム)のために設計された最初の言語、と位置づけられています。ソースコードはCPUの機械語ではなく中間言語(IL)にコンパイルされ、実行時にJITなどで機械語になります。
この仕組みの設計上の利点は次の通りです。
- 出力がCPUアーキテクチャに依存しない
- ランタイムが改良されると、再コンパイルなしで既存コードが恩恵を受ける
- 型情報がバイナリに残るため、シリアライズや単体テストの探索、リフレクションが成立する
「C#を理解するにはCLRの実行モデルを理解する必要がある」という前提が、後半のアセンブリ・リフレクション・メモリ管理の章につながっていきます。
3. .NETの実装と配布の整理
1章の後半は、.NET Framework、.NET Core、Monoという複数の実装の関係と、NuGetで配布するライブラリのターゲットをどう選ぶかを整理しています。
ライブラリ作者の観点では、.NET Standard が要点です。共通のAPIサブセットを対象にすることで、将来登場する実行環境でも再ビルドなしで動くようにする仕組みです。
- 自分のアプリ専用のコードなら、最新の.NET Coreを対象にする
- Windows固有機能が必要なら.NET Frameworkを対象にする
- 共有するライブラリなら.NET Standardを対象にする
この部分は執筆時点(.NET 5以前)の状況に基づいています。技術監修者による注記で、その後の.NET 5/6での統合の流れが補足されています。現在の.NETを使う場合は、最新の公式情報と照らして読むのがよいです。
4. 宣言空間という「コードを読む人のための規則」
2章のスコープと宣言空間の説明は、設計意図が分かりやすい箇所でした。
if (condition)
{
int count = 1;
}
int count = 2; // コンパイルエラー (CS0136)
2つの変数のスコープは重なっていないのに、このコードはエラーになります。C#は「スコープ」ではなく「宣言空間」という別の規則で、1つの名前が複数の実体を指さないようにしているためです。
変数宣言をブロックの先頭へ移動してもコードの意味が変わらないようにする、という考え方が背景にあります。読み手が名前の指す先を取り違えないための言語側の制約であり、可読性を言語仕様で担保する発想として参考になります。
5. 式とステートメント、評価順序を厳密に定義している
C#ではオペランドの評価順序が、ソースコード上の出現順として定義されています。副作用のある式をネストしたときの挙動を、本書は例を使って丁寧に追っています。
評価順序が未定義の言語では、副作用を伴う式を書いたときに挙動が実装依存になります。C#がこれを仕様で固定していることは、「予測可能性を優先する」という設計姿勢の表れです。
6. 数値型は「用途に合わせて選ぶ」もの
数値型の説明は、実務の設計判断に直結します。
-
intが既定の整数型なのは、CPUでの効率と実用的な範囲のバランスが良いため -
float/doubleは2進数表現のため、0.1のような小数を正確に表せず、誤差が累積する - 金額のように厳密さが求められる計算には、10進数ベースの
decimalを使う(代わりに計算は遅くなる)
整数のオーバーフローについては checked コンテキストの説明があります。
int total = checked(a + b); // オーバーフロー時に OverflowException
興味深いのは、タイマーの値のように「回り込む」ことが前提のカウンタでは、uncheckedの挙動が差分計算でうまく働く、という例です。オーバーフローを常に悪と見なさず、性質を理解して使い分ける姿勢が示されています。
7. 文字列は「文字の列」ではない
C#の string はUTF-16のコードユニットの列で、char は「1文字」とは限りません。結合文字やサロゲートペアがあるため、char 単位の操作には注意が必要です。
文字列に関する設計上のポイントは次の3つです。
- 文字列は不変(immutable)なので、引数として渡しても書き換えられない
- 大量の連結には可変の
StringBuilderを使う - 文字列補間はカルチャ依存の書式になりうるので、通信やシリアライズ用途ではカルチャを固定する
string s = FormattableString.Invariant($"{price:f2}");
実行環境のロケールで出力が変わるバグは見つけにくいため、この指摘は実務でも効きます。
8. デリゲートとインターフェイスの使い分け
9章では、コールバックの実現手段としてデリゲートとインターフェイスを比較しています。
- コールバックが1つだけなら、デリゲート(ラムダ)が軽い
- 関連する複数のコールバックをひとまとまりで提供するなら、インターフェイスが向く
Array.Sort(items, (x, y) => x.Priority.CompareTo(y.Priority));
.NETのUIフレームワークがインターフェイスではなくイベント(デリゲートが基礎)を採用しているのは、UI要素が提供する通知の数が多く、インターフェイスだと実装側の負担が大きくなるためです。一方でRxは、完了・エラー・次の値の通知が常にセットで必要なので、インターフェイスを採用しています。通知がバラバラに使われるのか、常にひとまとまりで使われるのか という観点は、そのまま自分のAPI設計に持ち込めます。
9. LINQを「プロバイダ」という設計で捉える
10章はLINQの構文だけでなく、その仕組みも扱っています。
LINQは言語機能と、LINQ演算子を実装するクラスライブラリ(プロバイダ)の2層でできています。同じクエリ構文でも、メモリ上のオブジェクト、データベース、XMLなど、プロバイダ次第で対象が変わります。
ここで鍵になるのが IEnumerable<T> と IQueryable<T> の違いです。
| 型 | 受け取るもの | 典型的な用途 |
|---|---|---|
IEnumerable<T> |
デリゲート | メモリ上のコレクションへのクエリ |
IQueryable<T> |
式ツリー | クエリを別の表現(SQLなど)へ変換する用途 |
var names = users
.Where(u => u.IsActive)
.OrderBy(u => u.Name)
.Select(u => u.Name);
見た目が同じコードでも、裏でコンパイラがデリゲートを渡すのか式ツリーを組み立てるのかが変わります。ORMがなぜクエリをSQLに変換できるのかを、言語機能のレベルから説明してくれる章です。
10. Rx:プルからプッシュへの反転
11章のReactive Extensions(Rx)は、LINQの「裏返し」として説明されます。
-
IEnumerable<T>:利用者が次の項目を要求する(プル型) -
IObservable<T>:準備ができたらソースが通知する(プッシュ型)
標準LINQ演算子の多くがRxでも使えるので、LINQの知識を転用できます。さらにRxは、時間を扱う演算子(Merge、Delay、DelaySubscription など)を備えています。イベントを第一級のオブジェクトとして扱えるため、引数として渡したり、フィールドに保持したりできます。エラー通知の仕組みが定義されている点も、素のイベントにはない強みとして挙げられています。
また、ホットソースとコールドソースの区別が演算子の挙動に影響する点も、設計時に意識すべき論点として説明されています。
11. アセンブリ・リフレクション・属性は「拡張可能な設計」の材料
12〜14章は、フレームワーク設計の基礎として読めます。
-
アセンブリ:デプロイの不可分な単位。型の識別の一部であり、
internalのアクセス制御の境界でもある - リフレクション:実行時に型情報を調べ、動作を変える。プラグインやデータ駆動のUI、単体テストフレームワークの基盤
- 属性:コードに付ける受動的な注釈。属性は誰かが探しに来たときだけ意味を持つ
属性を「荷物に貼る出荷ラベル」にたとえた説明は分かりやすく、「宣言的に設定し、フレームワーク側が拾う」という設計パターンの本質をついています。
12. 非同期とメモリ効率:性能設計の章
後半の16〜18章は、性能を意識した設計の話です。
マルチスレッドと非同期の整理(16〜17章)
- 非同期は必ずしもマルチスレッドを意味しない
- 同期APIでスレッドをブロックすると、待っているだけのスレッドが増えてリソースを浪費する
-
async/awaitを使うと、非同期コードを同期コードのように書ける
async Task<string> FetchAsync(HttpClient client, string url)
{
return await client.GetStringAsync(url);
}
サーバーアプリケーションでは、スレッドの効率的な利用によって同時処理できるユーザー数が増えます。クライアントではUIの応答性が保てます。非同期の価値を「速さ」ではなく「リソース効率と応答性」として説明している点が腑に落ちます。
メモリ効率(18章)
GCのコストが無視できなくなるケースに対して、Span<T> などでヒープ割り当てを減らす手法を紹介しています。
ReadOnlySpan<char> span = "key=value".AsSpan();
int i = span.IndexOf('=');
ReadOnlySpan<char> key = span.Slice(0, i); // 新しい文字列を割り当てない
文字列の分割のように便利なAPIは、部分要素ごとにオブジェクトを割り当てるため、累積すると性能を圧迫します。この章は、抽象化の便利さと実行コストのトレードオフ を意識させてくれます。同時に、「通常は複雑なコードになるので、メリットがある場合にだけ使う」という線引きも明示されています。
気になった点
良い本ですが、読む前に知っておくとよい点もあります。
- 情報の鮮度:C# 8.0と.NET Core 3.0前後が前提です。新しい構文や.NETの統合後の状況は、公式ドキュメントで補う必要があります
- 分量:全体で非常に厚く、通読には時間がかかります。リファレンスとして章単位で読む使い方が現実的です
- 前提知識:ほかの言語での十分な経験が前提です。プログラミング初学者向けではありません
- Rxや大規模トピック:RxやTPLデータフローなど、本書でも「詳細は別の資料を参照」とされる領域があります
本書の中にも、すべての詳細を取り上げられない話題は「その分野の資料に譲る」と明示されています。入口として十分な説明があり、深掘りは別の資料で、という割り切りは設計書として誠実だと感じました。
どんな人におすすめか
おすすめできる人
- 他の言語に習熟していて、C#を体系的に学び直したい人
- 「なぜこういう仕様なのか」まで理解して、設計判断に活かしたい人
- LINQ、async/await、Rxなどの仕組みを、構文ではなく原理から押さえたい人
- ライブラリやフレームワークを設計・提供する立場の人
向かない人
- プログラミング自体が初めての人
- 最新のC#の機能(C# 9以降)だけを素早く知りたい人
- 短時間でコードの書き方だけを確認したい人
まとめ
『プログラミングC# 第8版』は、C#という言語を「設計の産物」として読み解く本でした。要点を5つにまとめます。
- C#は「専用より汎用」の機能を積み上げ、組み合わせで価値を出す設計
- CLRと一体で設計されているため、実行モデルの理解が言語理解に直結する
- 言語仕様の各所に「読み手の誤解を防ぐ」ための制約がある
- デリゲートとインターフェイス、
IEnumerable<T>とIQueryable<T>など、設計上の使い分けの基準が示される - 非同期やメモリ効率は「速さ」ではなく「リソース効率とコスト」の観点で説明される
文法を覚える本というより、設計判断の背景を学ぶ本 です。章単位で、必要になったタイミングで読み返す使い方もおすすめです。
参考
- 『プログラミングC# 第8版』 Ian Griffiths 著、木下 哲也 訳、鈴木 幸敏 技術監修、オライリー・ジャパン(ISBN 978-4-87311-936-6)
- 原書:『Programming C# 8.0』 Ian Griffiths 著、O'Reilly
- サンプルコード(原書リポジトリ)
- オライリー・ジャパン 書籍ページ