バックエンドのフレームワークを新規に選定する立場のエンジニアや、既存の.NET資産を引き継いだ運用担当者に向けて整理します。「Stack OverflowもGitHubも使っているから安心」という評判だけで採用や継続判断をすると、見落としがちな非機能要件の論点があります。
.NETはMicrosoftが開発した実行環境とライブラリ群の総称です。C#やF#、Visual Basicといった複数言語で書いたコードを、CIL(Common Intermediate Language、OSに依存しない中間形式)に変換し、CLR(Common Language Runtime、実行エンジン)上のJITコンパイラが実行時に機械語へ変換します。この仕組み自体は23年前から存在し、Windows専用だった時代からクロスプラットフォーム対応の土台が組み込まれていた点が長寿の理由として語られています。
ただし「長く使われている」という事実は、そのまま「今のプロジェクトに適している」を意味しません。ここでは非機能要件とアーキテクチャ判断の観点から、.NET採用・継続時に見落とされやすい3つの落とし穴を分解します。
何が起きるか:バージョン混在による保守コストの膨張
.NETには.NET Framework(Windows専用、旧世代)と.NET(旧.NET Core、クロスプラットフォーム、現行)という2系統が存在します。
この区別を知らずにプロジェクトを引き継ぐと、依存ライブラリの一部が.NET Framework専用のAPIに依存していて、Linuxコンテナへの移行やクラウドネイティブ化が進まないという事態が起きます。
さらにやっかいなのは、.NET本体は6か月ごとに新バージョンが出て、奇数バージョン(.NET 7、.NET 9など)はSTS(Standard Term Support、短期サポート)で約18か月、偶数バージョン(.NET 8、.NET 10など)はLTS(Long Term Support、長期サポート)で3年サポートという運用が続いている点です。サポート期限を意識せず選定すると、気づかないうちにEOL(End of Life、サポート終了)を迎えたランタイムを本番稼働させ続けるリスクを抱えます。
なぜ起きるか:クロスプラットフォームの土台と、移行の実態がずれている
原因を段階的に分解すると、まず「CILとJITによる実行時変換」という設計自体は本当にOS非依存です。同じアセンブリファイルがWindows・Linux・macOSで動くという説明は技術的に正確です。
しかし、この土台の上に載る周辺ライブラリやサードパーティ製パッケージは、Windows前提で書かれたものが長年蓄積してきました。Active Directory連携やレガシーなCOM相互運用など、.NET Framework時代の資産に依存したコードは、CLRの互換性だけでは解決できません。
つまり「フレームワークの実行モデルが移植性を持つこと」と「アプリケーション全体が移植可能であること」は別の問題です。この区別を曖昧にしたまま「.NETだからLinuxでも動く」と判断すると、移行フェーズで想定外の書き換えコストが発生します。
もう一つの原因は、ガベージコレクタ(GC、不要になったメモリ領域を自動的に回収する仕組み)の存在です。GCはメモリ管理系の不具合を大きく減らしますが、GCの一時停止(ポーズ)がレイテンシに影響するワークロードでは、AOTコンパイル(Ahead-of-Time、実行前に事前コンパイルする方式)やGCモードの調整を検討する必要が出てきます。「自動でやってくれるから安心」という理解だけで、高スループット・低レイテンシ要件のシステムに組み込むと、後からチューニングの余地が狭いことに気づくケースがあります。
自分のプロジェクトが該当するか確認する方法
以下の観点で、現在のプロジェクトがどちらの落とし穴に近いかを確認できます。
- 使用中のランタイムバージョンを確認する:
dotnet --infoを実行し、表示されるVersionとサポート区分(STS/LTS)を照合する - プロジェクトファイル(.csproj)の
<TargetFramework>タグを開き、net48のような旧.NET Framework表記か、net8.0のような現行.NET表記かを確認する - 依存パッケージの互換性を確認する:
dotnet list package --deprecatedやdotnet list package --vulnerableでパッケージレベルのリスクを洗い出す - Microsoft公式のサポートポリシーページで、現在使っているバージョンのEOL日を確認する
- コンテナ化・Linux移行の計画があるなら、依存パッケージにWindows専用API(
System.Windows系やレジストリ操作など)への参照がないか、ソース検索で確認する
dotnet --info
dotnet list package --deprecated
dotnet list package --vulnerable特に dotnet list package --vulnerable は既知の脆弱性を含む依存関係を洗い出すコマンドで、セキュリティ観点の非機能要件チェックとして継続的に実行する価値があります。
対策の手順
確認結果に応じて、次の手順で対応します。
1. EOLが近い、または過ぎているバージョンを使っている場合は、直近のLTSバージョンへの移行計画を立てます。奇数バージョンから偶数バージョンへ移るケースが多く、dotnet-upgrade-assistant のようなMicrosoft公式ツールでコード変更点を事前に洗い出せます。
2. 依存パッケージにWindows専用API依存が見つかった場合は、代替となるクロスプラットフォームAPIへの置き換えを検討します。置き換えが難しい場合は、そのモジュールだけWindowsコンテナで運用を続けるという判断も現実的な選択肢です。
3. レイテンシに敏感なワークロードでは、GCのServer modeとWorkstation modeの違いを.csprojやruntimeconfig.jsonで確認し、必要に応じてAOTコンパイルへの切り替えを検証します。切り替えは起動速度とメモリ消費のトレードオフになるため、負荷試験で数値を取ってから判断します。
4. チーム引き継ぎを見据えて、フレームワークバージョンとサポート期限をREADMEやアーキテクチャ決定記録(ADR)に明記します。「なぜこのバージョンを選んだか」を書き残すことが、次の担当者への最大の贈り物になります。
まとめ
.NETが23年稼働し続けている事実は、CILとJITによる実行時変換という設計が堅牢だったことを示しています。
一方で、その堅牢性は「今のプロジェクトが自動的に恩恵を受ける」ことを保証しません。バージョン系統の違い、サポート期限、周辺ライブラリの移植性は個別に確認が必要です。
まずは dotnet --info と dotnet list package --vulnerable を手元のプロジェクトで実行し、現在地を数値で把握することから始めてみてください。そこから見えるギャップが、次に取るべきアクションを具体的にしてくれます。