ASP.NET(.NET Framework 4.8)で.NET 8クラスライブラリ参照時にSystem.Runtime 8.0が見つからない原因と解決策

ASP.NET(.NET Framework 4.8)から別プロジェクトの .NET 8 クラスライブラリを参照したら、実行時に「System.Runtime 8.0 が見つからない」で起動できない。これは“ランタイムの違い”が原因です。本記事では、なぜ起きるのかと、現実的に直す手段を具体例つきで整理します。

目次

発生するエラーと状況

.NET Framework 4.8 の ASP.NET(System.Web)アプリで、参照先として .NET 8(net8.0)向けにビルドされたクラスライブラリ DLL を追加すると、アプリ起動時や該当機能を呼び出した瞬間に次のような例外で落ちることがあります。

Could not load file or assembly 'System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
or one of its dependencies. The system cannot find the file specified.

ポイントは「コンパイルは通る/参照も追加できるのに、実行すると落ちる」ことです。これは設定ミスというより、.NET の土台(ランタイム)が根本的に違うことが原因で、そのまま共存させる方向では解決しません。

項目状況よくある誤解
参照追加Visual Studio で DLL 参照やプロジェクト参照ができてしまう「参照できた=実行もできる」
ビルド呼び出しコードが少ないとビルドが通る場合がある「ビルドできた=互換性がある」
実行アプリ起動時/型ロード時に System.Runtime 8.0 が見つからず失敗「System.Runtime.dll を bin に置けば直る」

なぜ「System.Runtime 8.0 が見つからない」が起きるのか

結論から言うと、.NET Framework 4.8 と .NET 8 は“別のランタイム”であり、基本的に直接互換ではありません。ここを押さえると、エラーの意味が一気に腑に落ちます。

.NET Framework と .NET(.NET 8)は実行エンジンが違う

.NET Framework 4.8 の ASP.NET は、Windows に入っている .NET Framework(CLR 4.x)上で動きます。一方、.NET 8(モダン .NET)は CoreCLR と呼ばれる別の実行環境を前提にし、依存する基本ライブラリ(BCL)も配布形態も異なります。

分かりやすい違いとして、.NET Framework の中心は mscorlib.dll(古い世代の“コア”)ですが、.NET 8 の中心は System.Private.CoreLib.dll です。つまり、同じ C# で書かれた DLL に見えても、前提としている基盤が別物です。

ターゲット主な用途実行ランタイム代表的なWeb基盤特徴
net48レガシー資産、System.Web.NET Framework(CLR)ASP.NET(System.Web、Web Forms/MVC5 等)Windows 依存。GAC 文化。長期運用の現場が多い
net8.0新規/モダナイズ.NET 8(CoreCLR)ASP.NET Coreクロスプラットフォーム。ランタイムパックで依存解決
netstandard2.0共通ライブラリ(仕様)Framework/モダン双方で実行可能どちらでも利用可能API の共通部分に絞ることで互換性を確保

ロード時に「参照先のアセンブリ」を解決できずに落ちる

.NET の DLL(アセンブリ)は、内部に「この DLL はどのアセンブリに依存しているか」という参照情報を持っています。net8.0 向けにビルドされたライブラリは、多くの場合 System.Runtime, Version=8.0.0.0 のように .NET 8 の基盤アセンブリ群への依存を持ちます。

.NET Framework 側には System.Runtime 8.0 相当のアセンブリは存在しません(別世界のため)。そのため CLR が DLL を読み込む段階で「必要なアセンブリが見つからない」と判断し、FileNotFoundException や FileLoadException として表面化します。

なお、例外が出るタイミングは一様ではありません。ASP.NET の場合、次のタイミングで“初めて”対象 DLL がロードされ、そこで落ちることがあります。

  • アプリ起動時(アプリドメイン作成直後、アプリの初期化)
  • 該当クラスを初めて参照したとき(JIT、型ロード、静的コンストラクタ)
  • リフレクションで型を列挙したとき(DI コンテナのスキャン、コントローラ探索など)

ここで重要なのは、単に DLL をコピーすれば済む問題ではないことです。仮に System.Runtime.dll らしきものを配置しても、.NET 8 のライブラリが前提にしている他の依存関係や実行エンジン(CoreCLR)まで .NET Framework のプロセスに持ち込めるわけではありません。

解決策は「ターゲットを合わせる」か「プロセスを分ける」

この問題の解決は、発想を切り替えるのが最短です。つまり、“.NET Framework 4.8 の中に .NET 8 DLL をそのまま読み込ませる”のは成立しないので、次のどれかに寄せます。

方針現実度メリットデメリット向いているケース
ライブラリを netstandard2.0 にする高いFramework 4.8 と .NET 8 の両方から利用可能使える API が絞られる共通ロジックを複数アプリで使いたい
ライブラリを net48 にする高い既存の ASP.NET(System.Web)に最短で合わせられる.NET 8 の恩恵を受けにくい当面は Framework で運用し続ける
Webアプリを ASP.NET Core(.NET 8)へ移行中〜高.NET 8 ライブラリをそのまま使える。将来性が高い移行コストが大きい中長期でモダナイズを進めたい
別プロセス化(Web API/サービス化)して呼び出す中互換性問題を回避しやすい。段階移行に強い通信や運用が増える移行できない制約があり機能だけ切り出したい

対処1:クラスライブラリを netstandard2.0 にする(最有力)

「.NET Framework 4.8 の ASP.NET と、.NET 8(あるいは将来の .NET 9/10…)の両方で同じビジネスロジックを使いたい」なら、まず検討すべきは netstandard2.0 です。互換性の取りやすさと実務的な成功率が高く、移行戦略としても破綻しにくい選択肢です。

修正手順(基本)

  1. ライブラリ側の .csproj でターゲットを netstandard2.0 に変更する(またはマルチターゲットにする)
  2. ビルドエラーが出たら、使っている API/パッケージが netstandard2.0 に対応しているか確認し、代替 API かマルチターゲットで解決する
  3. ASP.NET 側は「DLL 参照」よりも「プロジェクト参照」に切り替える(参照の混在を防ぐ)
  4. Web 側の bin とライブラリ側の bin/obj を一度削除してからクリーンビルドする
  5. IIS へ配置して起動確認(ローカルで動いても配置で失敗するケースを潰す)

設定例:SDKスタイルの csproj でターゲットを変更する

.NET 8 のクラスライブラリ(多くは SDK スタイル)なら、.csproj を次のようにします。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
</Project>

もし「.NET 8 でも最適化版を使いたい」「Framework 向けには互換版に落としたい」という場合は、マルチターゲットが便利です。

<PropertyGroup>
  <TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
</PropertyGroup>

これで、同じプロジェクトから netstandard2.0 用 DLL と net8.0 用 DLL を同時に作れます。呼び出し側は自分のターゲットに合う方を自動的に選ぶため、.NET Framework 側は netstandard2.0 を読み、.NET 8 側は net8.0 を読みます。

条件付きコンパイルで「.NET 8 専用処理」を分岐する

netstandard2.0 では使えない API(例:一部の最新 I/O、特定の暗号API、生成済み正規表現の最適化など)を .NET 8 でだけ使いたい場合は、ターゲットごとにコードを分けます。

public static string GetFastPath()
{
#if NET8_0_OR_GREATER
    // .NET 8 でだけ利用したい最適化コード
    return SomeNet8OnlyApi();
#else
    // .NET Framework 4.8 でも動く互換コード
    return SomeCompatibleApi();
#endif
}

実務では「互換コードをデフォルトにしておき、.NET 8 だけ高速化する」パターンが扱いやすいです。互換層を薄く保てるため、将来 Web 側を ASP.NET Core に移行したときも自然に最適化が効きます。

ハマりどころ:依存する NuGet パッケージが netstandard2.0 をサポートしているか

ライブラリが参照している NuGet パッケージが netstandard2.0 をサポートしていないと、ターゲット変更時にビルドが通らなくなります。次の観点で整理するとスムーズです。

  • 業務ロジック系:大抵は netstandard2.0 で問題になりにくい(例:ドメインモデル、計算、バリデーション)
  • インフラ系:DB/HTTP/暗号/ファイルなどは依存が濃いので、パッケージの対応ターゲット確認が重要
  • 最新 API 依存:.NET 8 固有の API を前提にしている場合はマルチターゲットで逃がす

また、.NET Framework 側の ASP.NET から参照する場合は、DLL 参照ではなくプロジェクト参照にすることをおすすめします。ビルド順やコピーの挙動が安定し、実行環境との差分が出にくくなります。

対処2:ライブラリを .NET Framework 4.8(net48)向けに作り直す

「そのライブラリは当面この ASP.NET(.NET Framework 4.8)だけで使う」「共通化は後回しでいい」なら、ライブラリを net48 で作るのが最短です。互換性問題が消えるので、運用を急ぐ現場では現実的な選択肢になります。

修正手順(基本)

  1. ライブラリのターゲットを net48 に変更する(新規に .NET Framework クラスライブラリを作って移植してもよい)
  2. .NET 8 専用の NuGet/API を使っていないか確認し、Framework 対応版へ差し替える
  3. ASP.NET 側で参照を付け替え、古い DLL が残らないように bin をクリアしてからビルド

メリット:互換性トラブルの発生源を潰せる

同じランタイム(.NET Framework)に揃えるため、今回のような System.Runtime 8.0 依存で壊れることは起きません。既存の設定(web.config、machine.config、アセンブリ解決)も従来通りです。

デメリット:.NET 8 の機能を前提にした設計は持ち込めない

たとえば、.NET 8 で追加・改善された API やパフォーマンス最適化をそのまま使うことはできません。将来的に ASP.NET Core へ移行する意思があるなら、ライブラリを net48 に固定するよりも、前述の netstandard2.0 かマルチターゲットを検討した方が、移行時の手戻りが少ないケースが多いです。

なお、最近の C# 言語機能(例:record、nullable など)は「言語」側の話なので、net48 でもコンパイル自体は通ることがあります。ただし、機能によっては補助型(例:IsExternalInit)や属性が必要になるため、チームの標準化方針として“どこまで新しい文法を許可するか”も合わせて決めておくと揉めにくいです。

選択短期の安定中長期の移行おすすめ度
ライブラリを net48 に固定◎△(移行時に作り直しが出やすい)「とにかく動かす」が最優先なら有効
ライブラリを netstandard2.0○◎(移行が滑らか)共通化・将来性を両立したいなら最有力
マルチターゲット(netstandard2.0;net8.0)○◎(段階的に最適化できる)両取りしたい中規模以上の開発で強い

対処3:Web側を ASP.NET Core(.NET 8)へ移行して“同じ世界”に揃える

ライブラリ側を .NET 8 のまま活かしたい、あるいは今後の保守性・性能・開発体験を改善したい場合は、Web アプリ自体を ASP.NET Core(.NET 8)へ寄せるのが王道です。今回のエラーは「別ランタイムを混ぜた」ことが原因なので、Web 側も .NET 8 に乗せれば問題は自然消滅します。

ただし移行は“置き換え”に近い

ASP.NET(System.Web)と ASP.NET Core は同じ「ASP.NET」という名前でも内部構造が大きく違います。特に次の領域は影響が出やすいので、移行計画では先に棚卸しすると安全です。

  • 認証・認可(Forms 認証、Windows 認証、Owin、独自ミドルウェア)
  • HttpContext/Session/Application などの API の違い
  • Web.config 依存(モジュール、ハンドラ、リライト、カスタム設定)
  • 依存している IIS 構成や古いライブラリ(System.Web 前提)

「まず全部移す」のが難しい場合は、次の“段階移行”が現実的です。

別プロセス化:.NET 8 側を Web API として切り出す

.NET 8 ライブラリを直接参照する代わりに、.NET 8 で動く別プロセス(ASP.NET Core Web API、Windows サービス、コンソール常駐など)へ機能を寄せ、.NET Framework 側からは HTTP/JSON 等で呼び出します。ランタイムの衝突がなくなるため、互換性問題を回避しやすいのが利点です。

方式.NET Framework 側からの呼び出しメリット注意点
HTTP(REST)HttpClient 等で呼ぶ実装がシンプル。運用ノウハウが多いネットワーク越しの失敗を前提にリトライ設計が必要
メッセージング(キュー)キューに投入して非同期処理高負荷に強い。疎結合設計と運用がやや重くなる
ファイル連携ファイル出力→別プロセスで処理既存資産に馴染みやすい排他制御や監視が必要

「これをやれば直るはず」とやりがちな失敗例

互換性問題に慣れていないと、次のような“それっぽい対処”を試して時間を溶かしがちです。結論として、今回のケースでは根本解決になりません。

bin に System.Runtime.dll(8.0)を置く

エラー文だけ見ると「System.Runtime が無いなら置けばいい」と考えがちですが、.NET 8 ライブラリは System.Runtime だけで動くわけではありません。そもそも .NET Framework の CLR は .NET 8 の前提を満たせず、依存が連鎖して別の DLL が足りない、または読み込めない状態になります。

bindingRedirect(web.config)で 8.0 に寄せる

<assemblyBinding> の bindingRedirect は、同じ .NET Framework 世界の中でバージョン差を吸収するための仕組みです。別ランタイム(.NET 8)向けに作られた DLL を .NET Framework に“変換”する魔法ではありません。今回のようにメジャーバージョンが飛んでいる場合、むしろ副作用を増やしやすいです。

参照の「特定のバージョン」を False にする

これは NuGet 依存やアセンブリ解決で効くこともありますが、根本の不一致は解消しません。症状が変わるだけで、別の依存関係で落ちるケースが多いです。

原因切り分けに役立つチェックポイント

「本当に .NET 8 DLL を読み込もうとして失敗しているのか」「別の依存が原因なのか」を早く判断したい場合は、次を確認すると効率的です。

  • 参照先 DLL のターゲット:ライブラリの .csproj で <TargetFramework> が net8.0 になっていないか
  • 依存ツリー:ライブラリが参照する NuGet が net8.0 専用になっていないか
  • 例外の詳細:Could not load file or assembly の「最初に見つからない DLL 名」を確認(連鎖している場合がある)
  • Fusionログ:.NET Framework のアセンブリ解決ログ(fuslogvw.exe)で「どこを探して失敗したか」を見る
  • bin/obj のクリア:古い DLL が残っていると原因が見えにくいので一度削除してからビルド

特に Fusion ログは、参照解決の経路が可視化されるので、現場での切り分けが速くなります。「System.Runtime 8.0 を探しに行って見つからない」ことが確認できれば、方針はすぐに netstandard2.0/net48/移行のいずれかに絞れます。

よくある質問

.NET Standard 2.1 ではダメですか?

.NET Standard 2.1 は .NET(Core 系)寄りで、.NET Framework 4.8 からの参照が安定しない/そもそも対応していないケースが多いです。.NET Framework 4.8 と確実に共有したいなら netstandard2.0 を選ぶのが安全です。

どうして参照を追加できてしまうのですか?

Visual Studio の参照追加は「この DLL をコンパイル時に見えるようにする」操作で、実行時のランタイム互換まで保証するものではありません。実行時に CLR が依存関係を解決できなければ落ちます。今回のような“別ランタイム問題”はまさにその典型です。

共有したいのがモデル(DTO)だけなら?

DTO や共通の定数・列挙など、プラットフォーム依存が薄い領域は netstandard2.0 で切り出すのが非常に相性が良いです。逆に DB 接続や OS 依存の処理は無理に共通化せず、インターフェースだけ共有して実装を分ける(DI で差し替える)と、移行やテストも楽になります。

まとめ:最短で直すなら「netstandard2.0」か「net48」、将来性なら移行・分離

ASP.NET(.NET Framework 4.8)で System.Runtime 8.0 が見つからないのは、.NET 8 クラスライブラリを“別ランタイム”のプロセスに読み込ませようとしていることが原因です。対処は、共有したいなら netstandard2.0(必要ならマルチターゲット)、短期安定なら net48、将来性なら Web 側の ASP.NET Core 移行、難しければ別プロセス化が現実的です。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次