.NET エコシステムマップ — 言語・ランタイム・BCL の関係
- .NET エコシステムマップ — 言語・ランタイム・BCL の関係
- .NET の歴史 — Framework から一つの .NET へ
- CLR · Mono · IL2CPP · NativeAOT — ランタイムの分岐を比較する
- C# は言語、.NET はプラットフォーム、CLR はランタイムです。同じように聞こえますが、それぞれ異なる層に位置しています
- C# のコードは IL という中間言語にコンパイルされ、ランタイムがその IL を JIT または AOT でネイティブコードに変換して実行します
- Unity は Mono または IL2CPP という独自のランタイム上で .NET のサブセットを使用しています。「Unity は .NET ではない」ではなく、「Unity は .NET の特定の実装を使っている」というのが正確です
序論: 「.NET」という言葉の曖昧さ
開発ドキュメント・履歴書・ブログで「.NET」という単語は、驚くほどさまざまな意味で使われています。
- 「.NET でバックエンドを書いています」
- 「.NET Framework 4.8 専用のプロジェクトです」
- 「.NET 8 に移行しました」
- 「Unity は .NET ではありません」 / 「Unity も実質的に .NET です」
同じ単語が言語・ランタイム・標準ライブラリ・アプリケーションフレームワークを行き来しながら曖昧に使われているからです。一度整理しておかないと、以降の C# シリーズのほぼすべての記事がこの曖昧さの上で揺れてしまいます。
この記事の目標はただひとつです。.NET という言葉がどの層を指しているかを区別できるようになること。
歴史 (第 2 回) とランタイムの分岐 (第 3 回) は次回以降に扱います。今回は地図を描きます。
Part 1. 6 つのレイヤーに分ける
.NET は単一の技術ではなく、6 つの層が積み重なったタワーです。一層ずつ分離して見ると、「.NET」という単語が今どの層を指しているかを言えるようになります。
各層は直下の層にのみ依存します。C# のコードは Roslyn を経て IL になり、IL はランタイムで解釈され、ランタイムは BCL を基本ライブラリとして提供し、その上でアプリケーションフレームワークが動作します。
「.NET」という単語は文脈によってこの中のどの層を指しています。以降のすべての議論はこの地図を参照します。
Part 2. C# と .NET の関係
最も混同されやすいところから整理します。
C# は言語です
C# は ECMA-334 と ISO/IEC 23270 で標準化されたプログラミング言語です。文法と型システムを定義するドキュメントがあり、コンパイラ (Roslyn) がそのドキュメントを実装しています。C# は Java と同じレベルの概念です。
.NET はプラットフォームです
.NET は言語・ランタイム・BCL・SDK・アプリケーションフレームワークをすべて含むエコシステムの名称です。上記ダイアグラムの第 2 層から第 6 層までを総称します。
2 つの組み合わせ
したがって、次の 4 つはすべて互いに異なる主張です。
| 主張 | 指す対象 |
|---|---|
| 「C# で書いた」 | 第 1 層 (言語) — コンパイラ・ランタイムは未指定 |
| 「.NET 8 で書いた」 | 第 2〜6 層全体 — 特定バージョンを指定 |
| 「.NET Framework 4.8 専用だ」 | Windows 専用の .NET 実装 |
| 「Unity の C#」 | C# 言語 + Unity の特定ランタイム (Mono または IL2CPP) |
「C# と .NET は同じものではないですか?」という問いへの答えは、「C# は .NET の一層 (言語)」です。
Part 3. IL — 中間層がなぜ必要なのか
ダイアグラムで最も注目すべき層は第 3 層 ILです。ここを理解すると、以降のすべてのランタイムに関する議論が楽になります。
コンパイル・実行パイプライン
なぜ中間層 (IL) を挟むのか
C 言語ではコンパイラがソースコードを直接ネイティブコードに変換します。.c ファイルは Windows x64 用の .exe になるか、Linux ARM64 用のバイナリになります。一度コンパイルした成果物は特定の OS + 特定の CPU の組み合わせに縛られます。
.NET はこの構造を一段階分割しました。C# のソースはまず IL (Intermediate Language) という中間バイトコードにのみ変換されます。IL はハードウェアに非依存であり、OS にも非依存です。実際のネイティブ変換は実行時点 (JIT) またはデプロイ直前 (AOT) に、そのマシンがどのようなマシンかを知った後に行われます。
この構造の利点は 3 つあります。
① プラットフォーム独立性。 同じ .dll が Windows・Linux・macOS のどこでも実行できます。ランタイムがそのマシンのネイティブ命令に変換してくれるからです。
② 言語独立性。 IL に落とし込めさえすれば、C# 以外に F#・VB.NET も同じランタイム・BCL を使います。言語が変わっても下の層はそのままです。
③ ランタイム最適化。 JIT は実際に実行しているハードウェア情報と実行中の統計を見て最適化できます。静的コンパイラより遅く変換する代わりに、より多くの情報を使えるというトレードオフです。
JIT と AOT — 同じ IL、異なる変換タイミング
IL をネイティブコードに変換するタイミングは 2 種類あります。
- JIT (Just-In-Time): プログラムが実行されるそのマシンで、その瞬間に変換します。変換コストは実行時に含まれます。代わりにハードウェア情報を正確に知ることができます。デスクトップ .NET・Mono がこの方式です。
- AOT (Ahead-Of-Time): アプリをデプロイする前に事前にネイティブコードに変換しておきます。実行時には JIT がなくても動作します。iOS のように JIT を許可しないプラットフォームでは唯一の選択肢です。IL2CPP・NativeAOT がこの方式です。
この境界が、Unity の Mono vs IL2CPP の選択、モバイル・コンソールビルドの落とし穴、Reflection.Emit が IL2CPP で壊れる理由をすべて説明します。詳細は第 3 回で扱います。
Part 4. Runtime と BCL — 実行を担う 2 本の柱
第 3 層 (IL) までは「ソースから何が作られるか」の話でした。第 4・5 層は「作られたものがどのように実行されるか」の話です。
Runtime — IL を実際に動かす主体
Runtime (ランタイム) は IL を読んでネイティブに変換し実行するエンジンです。メモリ管理 (GC)、例外処理、型システム、スレッド管理、セキュリティをすべて担当します。Microsoft 公式用語集の定義はこうなっています。
A CLR handles memory allocation and management. A CLR is also a virtual machine that not only executes apps but also generates and compiles code on-the-fly using a JIT compiler. (Microsoft Learn — .NET glossary)
重要な点は、ランタイムはひとつではないということです。
- CLR — .NET Framework のランタイム。Windows 専用
- CoreCLR — .NET 5+ (旧 .NET Core) のランタイム。クロスプラットフォーム
- Mono — オープンソースのクロスプラットフォーム実装。Unity のデフォルトランタイム
- IL2CPP — Unity が開発した AOT ランタイム。IL を C++ に変換してネイティブコンパイル
- NativeAOT — .NET 公式の AOT デプロイモード
同じ C# コード・同じ IL でも、どのランタイム上で動くかによってパフォーマンス・メモリ特性・使用可能な機能が変わります。
BCL — すべてのランタイムが基本として提供するライブラリ
BCL (Base Class Library) は System.* 名前空間に属する標準ライブラリです。Console.WriteLine、List<T>、Dictionary<K,V>、File.ReadAllText、Task、CancellationToken — 当然のように使っているほぼすべてが BCL です。
Microsoft 公式用語集の定義:
A set of libraries that comprise the
System.*(and to a limited extentMicrosoft.*) namespaces. The BCL is a general purpose, lower-level framework that higher-level application frameworks, such as ASP.NET Core, build on. (Microsoft Learn — .NET glossary)
BCL の位置を一文でまとめると、「ランタイムは IL を動かすエンジンであり、BCL はそのエンジンの上で基本提供される標準ライブラリです。」 エンジンだけあってライブラリがなければ、Console.WriteLine("hi") の一行も動きません。
3 つの単語の関係整理
| 単語 | 層 | 例 | 役割 |
|---|---|---|---|
| Assembly | 第 3 層 (IL の束) | MyApp.dll、System.Collections.dll | IL を格納するファイル単位 |
| Runtime | 第 4 層 | CLR / CoreCLR / Mono / IL2CPP | IL を実際に動かすエンジン |
| BCL | 第 5 層 | System.* 名前空間 | ランタイムの上で基本提供されるライブラリ |
「.NET Runtime をインストールする」という言葉は、日常的には第 4 層 (エンジン) + 第 5 層 (BCL) のセットをインストールするという意味です。この 2 つはほぼ常にセットで配布されます。
Part 5. SDK vs Runtime — 開発者とユーザーの違い
デプロイの話をすると必ず出てくる質問があります。「SDK と Runtime のどちらをインストールすればいいですか?」
答えは役割によります。
- SDK (Software Development Kit): アプリを作る人が必要なもの。コンパイラ、CLI ツール (
dotnet)、ランタイム、BCL、テンプレートがすべて入っています - Runtime: アプリを実行するだけの人が必要なもの。エンジン (第 4 層) と BCL (第 5 層) のみが入っています。コンパイラはありません
確認コマンドはこうなります。
1
2
3
4
5
6
7
8
$ dotnet --list-sdks
8.0.404 [/usr/local/share/dotnet/sdk]
10.0.100 [/usr/local/share/dotnet/sdk]
$ dotnet --list-runtimes
Microsoft.NETCore.App 8.0.11 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.AspNetCore.App 8.0.11 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 10.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
開発者マシンには通常 SDK がインストールされており、SDK の中にランタイムが含まれています。サーバーや一般ユーザーのマシンにはランタイムだけインストールすれば十分です。
ただし、Self-contained デプロイでパッケージングすると、ランタイムをアプリ内に含めて配布できます。この場合、ユーザーのマシンには何もインストールする必要がありません。NativeAOT でビルドすると、単一のネイティブバイナリになります。第 4 層の選択肢はここまで続きます。
Part 6. Unity はどこに位置するのか
このブログの読者の多くはゲームプログラマーです。ですから、地図に Unity の位置を示してはじめて絵が完成します。
3 つの経路が同じ C#・同じ IL を共有しています。変わる層は第 4 層 (ランタイム) と第 6 層 (アプリケーションフレームワーク)だけです。Unity が「.NET ではないもの」に見える理由は単純に異なるランタイムを使っているからです。
Unity の 2 つのランタイム
Mono (JIT) — Unity エディターとデスクトップビルドのデフォルト。ビルドが速く、反復開発に有利です。Mono は Microsoft 公式用語集でも「.NET の実装のひとつ」として明記されています。Microsoft Learn — .NET implementations
IL2CPP (AOT) — iOS・WebGL・コンソールターゲットのデフォルトかつ強制選択。IL を C++ ソースに変換した後、各プラットフォームのネイティブツールチェーン (Xcode、Emscripten、コンソール SDK) でネイティブバイナリを生成します。iOS は OS が JIT を許可しないため、Mono はそもそも選択肢になりません。Unity Manual — IL2CPP overview
ゲームプログラマーがこの地図から得るべきもの
この地図が示す実用的な結論を 3 つだけ挙げます。
① Reflection.Emit が IL2CPP で壊れるという事実の本質は、IL2CPP が実行時に IL を新しく生成する JIT を持たないからです。第 4 層が変わった結果が上の層の API の動作にまで伝播します。
② Unity の「C#」は Unity が選んだランタイム・BCL の組み合わせに縛られます。 サーバー .NET で使える API が Unity では使えないことがあり、逆もまた然りです。.csproj の API Compatibility Level 設定がまさに第 5 層の範囲を決めるスイッチです。
③ 「Unity は .NET エコシステムに属するか?」という問いへの答えは明確です。 第 1〜3 層 (C#・Roslyn・IL) をそのまま使い、第 4 層 (ランタイム) だけ Unity 専用のものを使います。同じエコシステム内の異なる実装にすぎません。
まとめ
今回描いた地図の結論を 3 行で整理します。
- C# は第 1 層の言語であり、.NET は第 2〜6 層全体のプラットフォームです。両者は同じ層ではありません。
- IL という中間層が言語独立性・プラットフォーム独立性・ランタイム最適化を生み出すトレードオフの中心です。
- ランタイム (CLR・CoreCLR・Mono・IL2CPP・NativeAOT) は複数が共存しており、どのランタイム上で動くかが同じコードのパフォーマンス・制約・利用可能な API を決定します。
この地図は以降のシリーズのすべての記事が参照する座標系です。どの記事でも「この話はどの層の話か?」をまず問うことをお勧めします。
次回予告
- 第 2 回。.NET の歴史 — Framework から一本の .NET へ — 2002 年の .NET Framework 1.0 から 2020 年の .NET 5 統合、そして現在の .NET 10 までの系譜。なぜ「Core」が名前から消えたのか、なぜ .NET 4 が飛ばされたのかもここで解説します。
- 第 3 回。CLR・Mono・IL2CPP・NativeAOT — ランタイム分岐の比較 — 第 4 層の 5 つの実装を JIT/AOT・メモリ・リフレクション・ジェネリクスの観点から比較します。ゲームプログラマーにとって最も実用的な回になる予定です。
参考資料
- Microsoft Learn — .NET glossary · 公式用語集
- Microsoft Learn — .NET implementations · 実装概要
- Microsoft Learn — Common Language Runtime (CLR) overview · CLR 概要
- Unity Manual — IL2CPP overview · Unity IL2CPP ドキュメント
- Unity Manual — Scripting backends introduction · Mono vs IL2CPP の背景
