例外処理

【C#】FileLoadExceptionの原因別トラブルシューティングと解決ステップ

System.IO.FileLoadException はアセンブリやファイルの読み込みが阻害されたときに発生し、原因はバージョン不一致・依存 DLL の不足・x86/x64 ミスマッチ・アクセス権不足の四つに集約されます。

スタックトレースで対象 DLL と HRESULT を確認し、参照設定、配置、ビルド構成、権限を順に見直すことで多くの場合は解決できます。

FileLoadExceptionとは

例外の基本情報

FileLoadExceptionは、C#のプログラムでファイルやアセンブリの読み込みに失敗した際に発生する例外の一つです。

主にSystem.IO名前空間に属しており、ファイルの存在は確認できたものの、何らかの理由でファイルのロードが完了しなかった場合にスローされます。

特に.NETアプリケーションでは、アセンブリの読み込み時にこの例外が発生することが多く、依存関係の問題やバージョンの不整合、アクセス権限の不足などが原因となることが多いです。

この例外は、単にファイルが見つからない場合に発生するFileNotFoundExceptionとは異なり、ファイル自体は存在しているが、読み込み処理が途中で失敗した場合に使われます。

例えば、アセンブリのバージョンが異なる、ファイルがロックされている、またはセキュリティ制限でアクセスできない場合などが該当します。

FileLoadExceptionは、例外のプロパティとして、失敗したファイルのパスや原因となった内部例外を持つことが多く、これらの情報を活用して原因の特定やトラブルシューティングを行います。

発生条件の概要

FileLoadExceptionが発生する主な条件は以下の通りです。

  • アセンブリのバージョン不一致

参照しているアセンブリのバージョンが実行環境の期待するバージョンと異なる場合に発生します。

例えば、.NET Framework 4.0の環境で.NET Framework 2.0向けにビルドされたアセンブリを読み込もうとすると、互換性の問題で例外が発生します。

  • 依存関係の不足や不整合

アセンブリが依存している他のDLLが存在しない、またはバージョンが異なる場合に読み込みに失敗します。

依存関係の解決に失敗すると、FileLoadExceptionがスローされることがあります。

  • プラットフォームターゲットの不一致

64ビット環境で32ビットのアセンブリを読み込もうとしたり、その逆の場合に発生します。

プラットフォームの不一致は、BadImageFormatExceptionと関連することもありますが、FileLoadExceptionとして扱われるケースもあります。

  • ファイルのアクセス権限不足

アセンブリファイルや関連ファイルに対して読み取り権限がない場合、ファイルは存在していても読み込みに失敗します。

特にネットワークドライブや共有フォルダからの読み込み時に注意が必要です。

  • ファイルのロックや占有

他のプロセスがファイルをロックしている場合や、ファイルが破損している場合にも読み込みに失敗します。

  • セキュリティ制限

.NETのセキュリティポリシーやWindowsのセキュリティ設定により、アセンブリの読み込みが制限されている場合に発生します。

特に、インターネットからダウンロードしたファイルがブロックされているケースが多いです。

これらの条件は、単独で発生することもあれば、複数の要因が重なって発生することもあります。

FileLoadExceptionのエラーメッセージやスタックトレースを確認し、どの条件に該当するかを特定することがトラブルシューティングの第一歩となります。

発生タイミング別に見る症状

アプリ起動時

アプリケーションの起動時にFileLoadExceptionが発生するケースは、主にアプリケーションのメインアセンブリや必須の依存アセンブリの読み込みに失敗した場合です。

起動直後に例外がスローされるため、アプリケーションが正常に開始できず、即座にエラーメッセージが表示されることが多いです。

このタイミングでの例外は、以下のような状況で起こりやすいです。

  • バージョン不一致

例えば、App.configでのバージョンリダイレクト設定が不足している場合や、古いバージョンのアセンブリが参照されている場合に発生します。

  • プラットフォームターゲットの不一致

64ビット環境で32ビットアセンブリを読み込もうとした場合など、起動時にロードされるアセンブリのプラットフォームが合わないと例外が発生します。

  • 依存DLLの不足

起動に必要な依存DLLがbinフォルダに存在しない場合、起動時に読み込みに失敗します。

  • セキュリティ制限

アセンブリが信頼されていない場所にある場合や、ファイルがブロックされている場合に起動時に例外が発生します。

起動時のFileLoadExceptionは、アプリケーションの起動自体が阻害されるため、ログやエラーメッセージを詳細に確認し、原因を特定することが重要です。

動的ロード時

動的ロードとは、実行時にAssembly.LoadAssembly.LoadFromAssembly.LoadFileなどのメソッドを使ってアセンブリを読み込む処理を指します。

このタイミングでFileLoadExceptionが発生すると、動的に読み込もうとしたアセンブリのロードに失敗したことを意味します。

動的ロード時の例外は、以下のような原因が考えられます。

  • パスの誤りやファイルの存在確認不足

指定したパスにアセンブリが存在しない、またはファイル名が間違っている場合に発生します。

  • 依存関係の不足

動的に読み込んだアセンブリが依存しているDLLが見つからない場合、ロードに失敗します。

  • バージョンやプラットフォームの不一致

動的に読み込むアセンブリのバージョンやプラットフォームが実行環境と合わない場合に例外が発生します。

  • セキュリティ制限

ネットワーク経由で取得したアセンブリや信頼されていない場所にあるファイルは、動的ロード時に制限されることがあります。

動的ロード時のFileLoadExceptionは、通常のビルド時の依存関係とは異なり、実行時の環境やファイル配置に依存するため、パスの指定や依存DLLの配置を特に注意して確認する必要があります。

プラグイン読み込み時

プラグインシステムを実装している場合、外部のアセンブリをプラグインとして読み込む際にFileLoadExceptionが発生することがあります。

プラグインは通常、アプリケーションの起動後に動的に読み込まれるため、動的ロード時の例外と似ていますが、特有の注意点があります。

プラグイン読み込み時に起こりやすい問題は以下の通りです。

  • 依存関係の衝突

プラグインが参照するアセンブリのバージョンがアプリケーション本体や他のプラグインと異なり、競合が発生する場合があります。

  • AssemblyLoadContextの管理不足

.NET Core以降では、プラグインごとに独立したAssemblyLoadContextを使わないと、アセンブリの再読み込みやアンロードができず、FileLoadExceptionが発生することがあります。

  • プラットフォームターゲットの不一致

プラグインのビルド設定がホストアプリケーションと異なる場合、読み込みに失敗します。

  • セキュリティ制限やアクセス権限

プラグインの配置場所やファイルのアクセス権限が不十分な場合、読み込みに失敗します。

  • ファイルロックや破損

プラグインファイルが他のプロセスにロックされている、または破損している場合も例外が発生します。

プラグイン読み込み時のFileLoadExceptionは、プラグインの設計や配置、依存関係の管理が重要です。

特に複数のプラグインを扱う場合は、バージョン管理やロードコンテキストの分離を意識することがトラブル回避につながります。

主な原因一覧

アセンブリバージョン不一致

アセンブリのバージョン不一致は、FileLoadExceptionが発生する代表的な原因の一つです。

プロジェクトが参照しているアセンブリのバージョンと、実行時に読み込まれるアセンブリのバージョンが異なる場合に起こります。

たとえば、開発環境ではバージョン1.0.0.0のDLLを参照しているのに、実行環境にはバージョン2.0.0.0のDLLが存在する場合などです。

この不一致は、以下のような状況で発生しやすいです。

  • NuGetパッケージのバージョンが異なる
  • 複数のプロジェクト間で参照バージョンが統一されていない
  • bindingRedirect設定が不足している
  • 古いDLLがbinフォルダに残っている

バージョン不一致が原因の場合、エラーメッセージに「バージョンが一致しない」旨の記述が含まれることが多いです。

対策としては、App.configWeb.configbindingRedirectを設定してバージョンのリダイレクトを行うか、参照しているアセンブリのバージョンを統一することが有効です。

依存 DLL 不足

依存DLL不足は、読み込もうとしているアセンブリが依存している他のDLLが存在しない場合に発生します。

アセンブリ単体では動作せず、依存関係のDLLが欠けていると、FileLoadExceptionがスローされます。

この問題は、以下のケースで起こりやすいです。

  • 依存DLLがbinフォルダにコピーされていない
  • NuGetパッケージの復元が不完全
  • 依存DLLのバージョンが異なり、正しく解決できない
  • ネイティブDLLが不足している

依存DLL不足の場合、エラーメッセージに「依存関係の解決に失敗した」や「ファイルが見つからない」などの文言が含まれることがあります。

Dependency WalkerやVisual Studioの診断ツールを使って依存関係を確認し、必要なDLLを正しく配置することが重要です。

プラットフォームターゲット不一致

プラットフォームターゲットの不一致は、アプリケーションのビルド設定と参照しているアセンブリのビルド設定が異なる場合に発生します。

例えば、64ビット(x64)環境で32ビット(x86)用にビルドされたアセンブリを読み込もうとすると、FileLoadExceptionBadImageFormatExceptionが発生します。

主な原因は以下の通りです。

  • プロジェクトのプラットフォームターゲットがAny CPUで、依存アセンブリが特定のプラットフォームに限定されている
  • 64ビット環境で32ビットDLLを読み込もうとしている
  • Prefer 32-bitオプションの設定ミス

この問題を解決するには、プロジェクトのビルド設定でプラットフォームターゲットを依存アセンブリに合わせて明示的に設定することが必要です。

Visual Studioのプロジェクトプロパティから「ビルド」タブを開き、x86x64を選択します。

セキュリティ・アクセス権

アセンブリファイルや依存DLLに対するアクセス権限が不足している場合もFileLoadExceptionが発生します。

特に、ネットワークドライブや共有フォルダ、ダウンロードしたファイルに対してWindowsのセキュリティ機能が働くことがあります。

よくある原因は以下の通りです。

  • ファイルがブロックされている(右クリック→プロパティ→「ブロックの解除」)
  • 実行ユーザーに読み取り権限がない
  • Windows Defenderやアンチウイルスソフトによるアクセス制限
  • ネットワークパスの信頼性設定不足

アクセス権限が原因の場合、エラーメッセージに「アクセスが拒否されました」や「セキュリティ制限」などの文言が含まれます。

ファイルのプロパティを確認し、必要に応じて権限を付与したり、ファイルのブロックを解除してください。

ネイティブライブラリのロード失敗

マネージドアセンブリがネイティブライブラリ(C++で作成されたDLLなど)に依存している場合、そのネイティブDLLのロードに失敗するとFileLoadExceptionが発生することがあります。

特にP/Invokeを使っている場合に注意が必要です。

原因としては以下が挙げられます。

  • ネイティブDLLが配置されていない
  • ネイティブDLLのビット数がマネージドDLLと異なる
  • PATH環境変数にネイティブDLLのパスが含まれていない
  • ネイティブDLLの依存関係が不足している

ネイティブライブラリのロード失敗は、エラーメッセージに「DLLが見つからない」や「指定されたモジュールが見つかりません」などが含まれます。

Dependency WalkerなどのツールでネイティブDLLの依存関係を調査し、正しい場所に配置することが重要です。

ファイルロックや占有

読み込もうとしているファイルが他のプロセスによってロックされている場合、FileLoadExceptionが発生します。

ファイルが使用中でアクセスできないため、読み込みに失敗するのです。

よくある状況は以下の通りです。

  • 別のプロセスがファイルを開いている
  • ビルドやデプロイの途中でファイルがロックされている
  • シャドウコピーやホットリロード機能が影響している

ファイルロックが原因の場合、エラーメッセージに「ファイルが使用中」や「アクセスできません」などの文言が含まれます。

Handle.exeProcess Explorerなどのツールでロックしているプロセスを特定し、解放することが必要です。

パッケージの破損

NuGetパッケージやその他のパッケージが破損している場合もFileLoadExceptionが発生します。

パッケージのダウンロードや展開が途中で失敗したり、キャッシュが壊れていることが原因です。

破損の兆候は以下の通りです。

  • パッケージのDLLが正しく展開されていない
  • NuGetキャッシュに不整合がある
  • パッケージのバージョンが不完全に更新された

この場合、NuGetのキャッシュクリアやパッケージの再インストールが有効です。

コマンド例としては以下のようなものがあります。

  • dotnet nuget locals all --clear
  • Visual Studioの「NuGetパッケージマネージャー」から再インストール

パッケージ破損は見た目ではわかりにくいため、キャッシュクリアや再インストールを試すことがトラブル解決の近道です。

原因別トラブルシューティング

アセンブリバージョン不一致

エラーメッセージの特徴

アセンブリのバージョン不一致が原因の場合、エラーメッセージには以下のような文言が含まれることが多いです。

  • 「混在モードアセンブリはバージョン ‘v2.0.50727’ に対してビルドされており、追加の構成情報なしに v4.0 ランタイムで読み込めません」
  • 「Could not load file or assembly ‘xxx, Version=1.0.0.0, Culture=neutral, PublicKeyToken=…’ or one of its dependencies. The located assembly’s manifest definition does not match the assembly reference.」

これらは、実行時に参照しているアセンブリのバージョンと、実際に読み込まれたアセンブリのバージョンが異なることを示しています。

確認手順

  1. Visual Studioの「参照」からアセンブリのバージョンを確認します。
  2. 実行環境のbinフォルダにあるDLLのバージョンを調べます。
  3. App.configWeb.configbindingRedirect設定を確認します。
  4. Fusionログを有効にして詳細な読み込み情報を取得します。

Fusion ログの確認

Fusionログは、.NET Frameworkのアセンブリバインディングの詳細を記録するログです。

これを有効にすることで、どのアセンブリがどこから読み込まれたか、または失敗したかを確認できます。

有効化手順は以下の通りです。

  • レジストリエディタで以下のキーを作成・編集します

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Fusion

  • EnableLog (DWORD) を 1 に設定
  • ForceLog (DWORD) を 1 に設定
  • LogFailures (DWORD) を 1 に設定
  • LogResourceBinds (DWORD) を 1 に設定
  • ログファイルは %TEMP%\FusionLog に出力されます

Fusionログを解析し、どのバージョンのアセンブリが読み込まれようとしているかを確認してください。

NuGet パッケージのバージョン整合

NuGetパッケージを利用している場合、複数のプロジェクトで異なるバージョンのパッケージを参照しているとバージョン不一致が起こりやすいです。

以下の点を確認します。

  • ソリューション全体で同じバージョンのパッケージを使用しているか
  • packages.configPackageReferenceのバージョンが統一されているか
  • dotnet restoreやVisual Studioのパッケージマネージャーで最新の状態に更新されているか

解決アプローチ

  • bindingRedirectを設定してバージョンのリダイレクトを行います
  • 参照しているアセンブリのバージョンを統一します
  • 古いDLLをbinフォルダから削除し、再ビルドします
  • NuGetパッケージを全プロジェクトで同じバージョンに揃えます

bindingRedirect の設定

App.configWeb.configに以下のような設定を追加します。

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="ExampleAssembly" publicKeyToken="32ab4ba45e0a69a1" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

これにより、古いバージョンの参照を新しいバージョンにリダイレクトできます。

アセンブリ再ビルド

参照しているアセンブリのソースコードがある場合は、ターゲットフレームワークやバージョンを合わせて再ビルドし、最新のDLLを生成してから参照し直すことも効果的です。

依存 DLL 不足

エラーメッセージの特徴

依存DLLが不足している場合、以下のようなエラーメッセージが表示されることがあります。

  • 「Could not load file or assembly ‘xxx’ or one of its dependencies. The system cannot find the file specified.」
  • 「ファイルが見つかりません」や「依存関係の解決に失敗しました」

依存DLLが見つからないことを示すメッセージが多いです。

確認手順

  1. 実行環境のbinフォルダに必要なDLLがすべて存在するか確認します。
  2. 依存関係のあるDLLのバージョンを確認します。
  3. Visual Studioの「参照」設定でCopy LocalTrueになっているか確認します。
  4. Dependency Walkerなどのツールで依存関係を解析します。

Dependency Walker の利用

Dependency Walkerは、Windows用のツールで、DLLの依存関係を解析できます。

対象のDLLを開くと、どのDLLに依存しているか、欠けているDLLがあるかを視覚的に確認できます。

  • 依存DLLが赤く表示されている場合は不足している可能性が高いです
  • ネイティブDLLの依存関係も確認可能です

dotnet-trace による監視

.NET Core環境では、dotnet-traceを使って実行時のイベントを収集し、依存関係の問題を特定できます。

依存DLLのロード失敗時の詳細なスタックトレースを取得し、原因を絞り込めます。

解決アプローチ

  • 依存DLLをbinフォルダに正しく配置します
  • Visual StudioのCopy LocalTrueに設定し、ビルド時にDLLをコピーさせます
  • プロジェクト参照を再追加し、依存関係を正しく解決させます
  • ネイティブDLLが必要な場合は、適切な場所に配置し、PATH環境変数を設定します

CopyLocal 設定の確認

Visual Studioの参照設定で、依存DLLのCopy LocalプロパティがFalseになっていると、ビルド出力にDLLがコピーされません。

これをTrueに変更し、ビルド時にDLLが出力フォルダにコピーされるようにします。

プロジェクト参照の再追加

依存DLLがプロジェクト参照として正しく追加されていない場合、参照を一度削除してから再追加することで解決することがあります。

これにより、依存関係の解決が正しく行われるようになります。

プラットフォームターゲット不一致

エラーメッセージの特徴

プラットフォームターゲットの不一致が原因の場合、以下のようなエラーメッセージが表示されることがあります。

  • System.BadImageFormatException: 間違ったフォーマットのプログラムを読み込もうとしました。
  • 「Could not load file or assembly … An attempt was made to load a program with an incorrect format.」

これらは、32ビットと64ビットの不一致を示しています。

確認手順

  1. Visual Studioのプロジェクトプロパティで「ビルド」タブを開きます。
  2. 「プラットフォームターゲット」がAny CPUx86x64のどれかを確認します。
  3. 依存アセンブリのビルド設定を確認します。
  4. 実行環境のOSのビット数を確認します。

AnyCPU 設定の落とし穴

Any CPU設定は便利ですが、Prefer 32-bitオプションが有効になっていると、64ビット環境でも32ビットモードで動作します。

依存DLLが64ビット専用の場合、これが原因で例外が発生することがあります。

BadImageFormatException との関連

FileLoadExceptionと似た状況でBadImageFormatExceptionが発生しますが、両者は密接に関連しています。

プラットフォーム不一致による読み込み失敗は、どちらかの例外として現れることがあります。

解決アプローチ

  • プロジェクトのプラットフォームターゲットを依存DLLに合わせて固定する(例:x64)
  • Prefer 32-bitオプションを無効にします
  • 依存DLLのビルド設定をホストアプリケーションに合わせて再ビルドします

x86/x64 固定ビルド

Visual Studioのプロジェクト設定で、x86またはx64に明示的に設定し、依存DLLと一致させることが重要です。

これにより、プラットフォーム不一致による例外を防げます。

Prefer32Bit オプション

Any CPU設定のプロジェクトでは、Prefer 32-bitオプションがデフォルトで有効になっていることがあります。

64ビットDLLを使う場合は、このオプションを無効にしてください。

セキュリティ・アクセス権

エラーメッセージの特徴

アクセス権限が不足している場合、以下のようなメッセージが表示されることがあります。

  • 「アクセスが拒否されました」
  • 「セキュリティ制限によりファイルを読み込めません」
  • 「ファイルがブロックされています」

確認手順

  1. ファイルのプロパティを開き、「ブロックの解除」ボタンがあるか確認します。
  2. 実行ユーザーのファイルアクセス権限を確認します。
  3. Windows Defenderやアンチウイルスソフトのログを確認します。
  4. ネットワークパス(UNCパス)からの実行かどうかを確認します。

Windows Defender の確認

Windows Defenderや他のセキュリティソフトがファイルの読み込みをブロックしている場合があります。

ログを確認し、必要に応じて例外ルールを追加してください。

UNC パスと信頼性

ネットワーク共有(UNCパス)からアセンブリを読み込む場合、信頼性の問題で読み込みが制限されることがあります。

ローカルドライブにコピーして実行するか、信頼された場所として設定してください。

解決アプローチ

  • ファイルの「ブロックの解除」を行います
  • 実行ユーザーに読み取り権限を付与します
  • セキュリティソフトの例外設定を行います
  • ネットワークパスからの実行を避けるか、信頼済みサイトに追加します

ファイルアンブロック

ダウンロードしたDLLはWindowsによりブロックされていることがあります。

ファイルのプロパティを開き、「ブロックの解除」ボタンを押してアンブロックしてください。

AppDomainSetup の設定

アプリケーションドメインの設定で、信頼レベルやベースディレクトリを適切に設定することで、セキュリティ制限を回避できる場合があります。

ネイティブライブラリのロード失敗

エラーメッセージの特徴

ネイティブライブラリのロード失敗時は、以下のようなメッセージが表示されることがあります。

  • 「指定されたモジュールが見つかりません」
  • 「DLLが見つからない」
  • 「P/Invoke 呼び出しに失敗しました」

確認手順

  1. ネイティブDLLが正しい場所に配置されているか確認します。
  2. ネイティブDLLのビット数がマネージドDLLと一致しているか確認します。
  3. PATH環境変数にネイティブDLLのパスが含まれているか確認します。
  4. P/Invokeの署名が正しいか確認します。

P/Invoke の署名チェック

P/Invokeで宣言した関数の署名がDLLの実装と一致していないと、ロードに失敗します。

引数の型や呼び出し規約を正しく指定してください。

PATH 環境変数の調査

ネイティブDLLがPATHに含まれるディレクトリに存在しない場合、ロードに失敗します。

環境変数を確認し、必要に応じてパスを追加してください。

解決アプローチ

  • ネイティブDLLを実行ファイルの近くに配置する(xcopy配置)
  • RID(Runtime Identifier)固有のパッケージを追加し、環境に合ったDLLを利用します
  • P/Invokeの署名を修正します
  • PATH環境変数を適切に設定します

xcopy 配置

ビルド後イベントなどで、ネイティブDLLを実行ファイルの出力フォルダにコピーする方法です。

これにより、実行時にDLLが見つかりやすくなります。

RID 固有パッケージの追加

.NET Coreや.NET 5以降では、RID固有のパッケージを利用してプラットフォームに合ったネイティブDLLを自動的に取得できます。

RuntimeIdentifiersを設定し、適切なパッケージを追加してください。

ファイルロックや占有

エラーメッセージの特徴

ファイルがロックされている場合、以下のようなメッセージが表示されることがあります。

  • 「ファイルが使用中のため、プロセスはファイルにアクセスできません」
  • 「アクセスが拒否されました」

確認手順

  1. Handle.exeProcess Explorerを使って、どのプロセスがファイルをロックしているか特定します。
  2. シャドウコピーやホットリロード機能が有効か確認します。
  3. ビルドやデプロイのタイミングを確認します。

Handle.exe でプロセス特定

MicrosoftのSysinternalsツールであるHandle.exeを使い、特定のファイルをロックしているプロセスを調べられます。

handle.exe YourAssembly.dll

これでロックしているプロセスIDがわかります。

シャドウコピー環境の確認

Visual Studioのテスト実行や一部のホスティング環境ではシャドウコピーが使われており、ファイルのロック状態に影響を与えることがあります。

設定を見直してください。

解決アプローチ

  • ロックしているプロセスを終了またはファイルの使用を解除します
  • ビルド後イベントのタイミングを調整し、ファイルアクセスの競合を避けます
  • CI/CD環境でクリーンビルドを行い、古いファイルのロックを防止します

ビルド後イベントの調整

ビルド後にファイルをコピーや削除する処理がある場合、ファイルがロックされていると失敗します。

イベントの順序やタイミングを見直してください。

CI/CD でのクリーンビルド

継続的インテグレーション環境で古いファイルがロックされている場合、クリーンビルドを行い、ファイルの競合を防止します。

パッケージの破損

エラーメッセージの特徴

パッケージ破損が原因の場合、以下のようなメッセージが表示されることがあります。

  • 「ファイルが壊れているか、読み込めません」
  • 「パッケージの展開に失敗しました」

確認手順

  1. NuGetパッケージのZIPファイルを手動で展開し、整合性を確認します。
  2. NuGetキャッシュをクリアします。
  3. Visual Studioのパッケージマネージャーで再インストールを試みる。

ZIP の整合性チェック

NuGetパッケージはZIP形式で配布されているため、破損していると展開に失敗します。

手動で展開できるか確認してください。

NuGet キャッシュクリア

以下のコマンドでキャッシュをクリアします。

dotnet nuget locals all --clear

これにより、破損したキャッシュが削除され、再取得が促されます。

解決アプローチ

  • パッケージを再インストールします
  • キャッシュをクリアしてから再取得します
  • パッケージソースの設定を見直し、信頼できるソースから取得します

再インストール

Visual Studioの「NuGetパッケージマネージャー」から対象パッケージをアンインストールし、再度インストールします。

パッケージソースの見直し

信頼できないパッケージソースやミラーサイトを使っている場合、破損したパッケージが配布されることがあります。

公式のNuGet.orgなど信頼できるソースを利用してください。

デバッグツール活用

Fusion ログビューワー

インストール手順

Fusionログは、.NET Frameworkのアセンブリバインディングの詳細を記録する機能で、FileLoadExceptionの原因特定に非常に役立ちます。

Fusionログを効率的に閲覧するためのツールとして「Fusion Log Viewer(Fuslogvw.exe)」があります。

Fusion Log Viewerは、通常Visual Studioのインストールに含まれているため、別途ダウンロードする必要はありません。

以下の手順で起動・利用できます。

  1. Visual Studio Developer Command Promptを起動

スタートメニューから「Developer Command Prompt for VS」を検索して起動します。

  1. Fusion Log Viewerを起動

コマンドプロンプトで以下を入力します。

fuslogvw 3. ログの設定

Fusion Log Viewerのウィンドウが開いたら、Settingsボタンをクリックし、

  • 「Log bind failures to disk」
  • 「Enable custom log path」(任意でログ保存先を指定)

をチェックします。

これでアセンブリの読み込み失敗時のログが記録されます。

  1. アプリケーションを実行して例外を再現

Fusionログに読み込みの詳細が記録されます。

  1. ログの確認

Fusion Log Viewerのリストに表示されるログをダブルクリックすると、詳細なバインド情報が表示されます。

ログ解析ポイント

Fusionログの解析では、以下のポイントに注目してください。

  • バインド要求のパス

どのパスからアセンブリを探しているかがわかります。

期待している場所と異なる場合は、配置ミスの可能性があります。

  • バージョン情報

実際に読み込もうとしたアセンブリのバージョンと、要求されたバージョンが一致しているか確認します。

  • 失敗理由

「LOG: HRESULT = 0x80070002」などのエラーコードが記録されている場合、ファイルが見つからないことを示します。

「LOG: The located assembly’s manifest definition does not match the assembly reference」ならバージョン不一致です。

  • リダイレクト情報

bindingRedirectが正しく機能しているかもログに記録されます。

これらの情報をもとに、どのアセンブリがどこで失敗しているかを特定し、問題解決に役立てます。

Visual Studio 診断ツール

例外設定のカスタマイズ

Visual Studioでは、例外が発生した際にデバッガーが自動的に停止するように設定できます。

FileLoadExceptionのような特定の例外で停止させるには、以下の手順で例外設定をカスタマイズします。

  1. Visual Studioのメニューから「デバッグ」→「例外設定」を開きます。
  2. 「Common Language Runtime Exceptions」のツリーを展開します。
  3. System.IO.FileLoadExceptionを探し、チェックボックスをオンにします。
  4. これで、FileLoadExceptionがスローされた時点でデバッガーが停止し、スタックトレースや変数の状態を確認できます。

この設定により、例外の発生箇所を特定しやすくなり、原因調査が効率的に行えます。

Output ウィンドウの読み方

Visual StudioのOutputウィンドウは、ビルドやデバッグ時のログを表示します。

FileLoadExceptionの原因を探る際には、以下の情報に注目してください。

  • Fusionバインドログ

Fusionログを有効にしている場合、Outputウィンドウにアセンブリのバインドに関する詳細が表示されます。

=== Pre-bind state information === LOG: DisplayName = ExampleAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=... LOG: Appbase = file:///C:/MyApp/bin/Debug/ LOG: Binding succeeded. または LOG: HRESULT = 0x80070002(失敗)

  • 例外のスタックトレース

例外が発生したメソッドや呼び出し元の情報が表示されます。

これにより、どのコードが例外を引き起こしているかがわかります。

  • 依存関係のロード状況

依存DLLのロードに失敗している場合、その情報も出力されることがあります。

これらのログを読み解くことで、どのアセンブリの読み込みに問題があるか、どの依存関係が不足しているかを把握できます。

dotnet CLI

–additionalprobingpath の活用

.NET Coreや.NET 5以降の環境では、アセンブリの検索パスを追加で指定できる--additionalprobingpathオプションがあります。

これを使うと、標準のプローブパス以外の場所からもアセンブリを探すことが可能です。

例えば、カスタムフォルダに配置した依存DLLを読み込ませたい場合に有効です。

dotnet exec --additionalprobingpath /path/to/custom/libs MyApp.dll

このオプションを使うことで、依存関係の解決に失敗している場合のトラブルシューティングに役立ちます。

コマンド例

  • アプリケーションの実行時に追加のプロービングパスを指定する例
dotnet exec --additionalprobingpath C:\MyCustomLibs MyApp.dll
  • NuGetキャッシュの場所を指定して復元する例
dotnet restore --packages C:\NuGetCache
  • 依存関係のトレースを取得する例
dotnet trace collect --process-id 12345 --providers Microsoft-DotNETCore-SampleProfiler

これらのコマンドを活用することで、依存関係の問題やFileLoadExceptionの原因をより詳細に調査できます。

特に--additionalprobingpathは、標準のパスにないDLLを読み込む際に非常に便利です。

.NET Core と .NET Framework の違い

アセンブリ解決の仕組み

.NET Frameworkと.NET Coreでは、アセンブリの解決(ロード)方法に大きな違いがあります。

これがFileLoadExceptionの発生やトラブルシューティングに影響を与えます。

.NET Frameworkのアセンブリ解決

.NET Frameworkでは、アセンブリの解決は主に以下の順序で行われます。

  1. グローバルアセンブリキャッシュ(GAC)

共有アセンブリはGACに登録されている場合、まずここから探されます。

  1. アプリケーションのベースディレクトリ

実行ファイルのあるディレクトリやbinフォルダから探します。

  1. App.configprobing設定

サブフォルダの指定があればそこも検索対象になります。

  1. bindingRedirectの適用

バージョンのリダイレクト設定があれば、それに従ってバージョンを調整します。

この仕組みは比較的固定的で、Fusionログを使って詳細なバインド情報を取得できます。

.NET Coreのアセンブリ解決

.NET Coreでは、アセンブリ解決の仕組みがより柔軟かつ複雑です。

  • deps.jsonファイルの利用

アプリケーションの依存関係はdeps.jsonファイルに記述されており、ここでどのアセンブリをどのパスから読み込むかが管理されています。

  • ランタイム識別子(RID)による依存関係の切り替え

OSやアーキテクチャに応じて異なるネイティブライブラリやアセンブリを選択します。

  • AssemblyLoadContextによる動的ロード

.NET Coreは複数のAssemblyLoadContextを使い、アセンブリの分離やアンロードを可能にしています。

これにより、プラグインシステムなどで柔軟なロードが可能ですが、管理が複雑になります。

  • グローバルアセンブリキャッシュは存在しない

GACは.NET Coreにはなく、すべての依存はアプリケーションのローカルフォルダやNuGetパッケージから解決されます。

このため、.NET Coreでは依存関係の管理がより厳密で、FileLoadExceptionの原因特定にはdeps.jsonAssemblyLoadContextの理解が重要です。

RuntimeIdentifiers の影響

RuntimeIdentifiers(RID)は、.NET Core/.NET 5以降のプロジェクトファイルで指定する、ターゲットとする実行環境の識別子です。

これにより、OSやCPUアーキテクチャに特化した依存関係の解決やネイティブライブラリの選択が行われます。

RIDの役割

  • プラットフォーム固有のパッケージ選択

例えば、Windows x64用のネイティブDLLとLinux ARM用のDLLを区別して取得できます。

  • ビルド時の依存関係解決

RIDを指定することで、ビルド時に適切なネイティブライブラリやアセンブリが含まれます。

  • 実行時のアセンブリロードに影響

RIDが異なると、deps.jsonに記載される依存関係が変わり、ロードされるアセンブリも変わります。

RID指定の例

<PropertyGroup>
  <RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>

このように複数のRIDを指定すると、マルチプラットフォーム対応のビルドが可能です。

RIDが原因のトラブル

  • RIDが正しく指定されていないと、実行時に必要なネイティブライブラリが見つからずFileLoadExceptionが発生することがあります
  • RIDごとに異なる依存関係があるため、ビルドやデプロイ時に適切なRIDを選択しないと問題が起こります

アップグレード時の注意

.NET Frameworkから.NET Core/.NET 5以降へアップグレードする際には、アセンブリ解決の違いに注意が必要です。

以下のポイントを押さえておくとトラブルを防げます。

依存関係の管理方法の違い

  • .NET FrameworkではGACやbindingRedirectでバージョン管理を行うのに対し、.NET Coreではdeps.jsonとNuGetパッケージで依存関係を厳密に管理します
  • そのため、アップグレード時には依存パッケージのバージョンを明示的に揃え、RuntimeIdentifiersを適切に設定する必要があります

ネイティブライブラリの扱い

  • .NET Coreではネイティブライブラリの配置やロード方法が異なり、RIDに応じたネイティブDLLの管理が必要です
  • 以前の.NET Frameworkの配置方法をそのまま使うと、ロード失敗やFileLoadExceptionが発生しやすくなります

bindingRedirectの非対応

  • .NET CoreではbindingRedirectがサポートされていません。バージョンの不一致はNuGetパッケージのバージョン管理で解決する必要があります

AssemblyLoadContextの理解

  • .NET CoreではAssemblyLoadContextがアセンブリのロードを管理しているため、プラグインや動的ロードの実装を見直す必要があります

実行環境の違い

  • .NET Coreはクロスプラットフォーム対応のため、Windows以外の環境で動作させる場合はパス区切り文字や大文字小文字の扱いなども考慮が必要です

これらの違いを理解し、アップグレード計画を立てることで、FileLoadExceptionなどのトラブルを未然に防げます。

マルチプラットフォーム環境での注意点

Windows と Linux のパス区切り

WindowsとLinuxでは、ファイルパスの区切り文字が異なるため、マルチプラットフォーム対応のアプリケーション開発では注意が必要です。

Windowsはバックスラッシュ\、Linuxはスラッシュ/をパス区切りに使います。

パス区切りの違いによる問題

  • ハードコーディングされたパス

Windows用に\で区切ったパスをLinuxでそのまま使うと、ファイルが見つからずFileLoadExceptionなどの例外が発生します。

  • パス結合の不適切な実装

文字列連結でパスを作成すると、環境によって区切り文字が異なり、正しいパスにならないことがあります。

対策方法

  • Path.Combineの利用

.NETのSystem.IO.Path.Combineメソッドを使うと、環境に応じた正しい区切り文字でパスを結合できます。

using System;
using System.IO;
class Program
{
    static void Main()
    {
        string folder = "data";
        string fileName = "config.json";
        string fullPath = Path.Combine(folder, fileName);
        Console.WriteLine(fullPath);
    }
}
// Windowsの場合
data\config.json
// Linuxの場合
data/config.json
  • Path.DirectorySeparatorCharの活用

区切り文字を明示的に使いたい場合は、Path.DirectorySeparatorCharを使うと環境に依存しないコードになります。

  • URI形式のパス利用

一部のAPIではスラッシュ/を使ったURI形式のパスが使えます。

Linux互換性を考慮する場合はこちらも検討してください。

注意点

  • 環境変数や設定ファイルにパスを記述する場合も、環境に合わせて区切り文字を変えるか、スラッシュを使うことが多いです
  • 外部ライブラリやプラグインがWindows用のパスを前提としている場合、Linuxでの動作に問題が出ることがあります

大文字小文字の区別

WindowsとLinuxではファイルシステムの大文字小文字の扱いが異なります。

WindowsのNTFSは基本的に大文字小文字を区別しませんが、Linuxのファイルシステム(ext4など)は大文字小文字を区別します。

大文字小文字の違いによる問題

  • ファイル名の不一致

WindowsではConfig.jsonconfig.jsonは同じファイルとして扱われますが、Linuxでは別ファイルとして扱われます。

そのため、Linux環境でファイル名の大文字小文字が異なると、ファイルが見つからずFileLoadExceptionFileNotFoundExceptionが発生します。

  • アセンブリ名やリソース名の不一致

アセンブリの名前やリソースファイルのパスで大文字小文字が異なると、Linux環境で読み込みに失敗することがあります。

対策方法

  • ファイル名の命名規則を統一する

プロジェクト内でファイル名やフォルダ名の大文字小文字を厳密に統一し、すべて小文字またはキャメルケースなどに統一します。

  • コード内のパス指定も統一する

ファイルアクセス時のパス文字列は、実際のファイル名と完全に一致させることが重要です。

  • CI/CD環境でLinuxビルドを行い検証する

Windows環境だけでなく、Linux環境でもビルド・テストを行い、大文字小文字の問題を早期に発見します。

  • Gitの設定を確認する

GitはWindowsとLinuxで大文字小文字の扱いが異なるため、.gitattributescore.ignorecaseの設定を適切に行い、ファイル名の大文字小文字の変更を正しく管理します。

マルチプラットフォーム対応では、パスの区切り文字と大文字小文字の違いが原因でファイルの読み込みに失敗しやすいです。

Path.Combineの利用や命名規則の統一、Linux環境でのテストを徹底することで、FileLoadExceptionなどのトラブルを防げます。

依存性注入コンテナ利用時の落とし穴

プラグインロードシナリオ

依存性注入(DI)コンテナを利用している環境で、プラグインを動的にロードするシナリオでは、いくつかの注意点があります。

プラグインは外部のアセンブリとして動的に読み込まれ、DIコンテナに登録して利用することが多いですが、この過程でFileLoadExceptionなどの例外が発生しやすくなります。

主な問題点

  • アセンブリの重複読み込み

プラグインアセンブリが既にロードされている場合に、同じアセンブリを再度読み込もうとすると例外が発生します。

DIコンテナがプラグインの型を解決しようとした際に、アセンブリのバージョンやロードコンテキストの違いで衝突が起こることがあります。

  • 依存関係の競合

プラグインが依存するアセンブリのバージョンがホストアプリケーションや他のプラグインと異なる場合、依存関係の解決に失敗し、例外が発生します。

  • DIコンテナのスコープ管理

プラグインのライフサイクル管理が適切でないと、アセンブリのアンロードや再読み込みができず、メモリリークや例外の原因になります。

対策例

  • プラグインごとに独立したAssemblyLoadContextを利用し、アセンブリの分離を行います
  • DIコンテナに登録する際は、プラグインの依存関係を明確にし、バージョンの整合性を保ちます
  • プラグインのロードとアンロードのタイミングを明確にし、不要になったプラグインは適切にアンロードします
  • プラグインのインターフェースを共通化し、DIコンテナにはインターフェースのみを登録することで、実装の差異による衝突を避けます

AssemblyLoadContext の管理

.NET Core以降の環境では、AssemblyLoadContext(ALC)がアセンブリのロードとアンロードを管理する重要な仕組みです。

DIコンテナと組み合わせてプラグインを扱う場合、ALCの管理が不十分だとFileLoadExceptionやメモリリークの原因になります。

AssemblyLoadContextの役割

  • アセンブリのロード単位を分離し、同じ名前の異なるバージョンのアセンブリを共存させます
  • アセンブリのアンロードを可能にし、動的にプラグインを入れ替えられます
  • ホストアプリケーションのアセンブリとプラグインのアセンブリを分離して管理します

管理上の注意点

  • デフォルトのAssemblyLoadContextに依存しない

すべてのアセンブリをデフォルトのコンテキストにロードすると、アンロードができず、プラグインの再読み込みができません。

  • カスタムAssemblyLoadContextの実装

プラグインごとにカスタムのALCを作成し、依存関係の解決やロードの制御を行う必要があります。

  • 依存アセンブリの解決イベントのハンドリング

カスタムALCでは、Resolvingイベントを利用して依存アセンブリのロード先を指定し、競合を回避します。

  • アンロードのタイミング管理

プラグインをアンロードする際は、ALCのUnloadメソッドを呼び出し、GCを促す必要があります。

これを怠るとメモリが解放されず、再読み込み時に例外が発生します。

サンプルコード例

using System;
using System.Reflection;
using System.Runtime.Loader;
public class PluginLoadContext : AssemblyLoadContext
{
    private AssemblyDependencyResolver _resolver;
    public PluginLoadContext(string pluginPath) : base(isCollectible: true)
    {
        _resolver = new AssemblyDependencyResolver(pluginPath);
    }
    protected override Assembly Load(AssemblyName assemblyName)
    {
        string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
        if (assemblyPath != null)
        {
            return LoadFromAssemblyPath(assemblyPath);
        }
        return null;
    }
}
class Program
{
    static void Main()
    {
        string pluginPath = @"C:\Plugins\MyPlugin.dll";
        var loadContext = new PluginLoadContext(pluginPath);
        Assembly pluginAssembly = loadContext.LoadFromAssemblyPath(pluginPath);
        // プラグインの型を取得し、DIコンテナに登録するなどの処理を行う
        // アンロード
        loadContext.Unload();
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("プラグインをアンロードしました。");
    }
}

この例では、プラグインごとに独立したAssemblyLoadContextを作成し、依存関係の解決をカスタマイズしています。

アンロードも可能なため、プラグインの動的な入れ替えに対応できます。

DIコンテナとプラグインロードを組み合わせる場合は、AssemblyLoadContextを適切に管理し、アセンブリの分離とアンロードを意識することが重要です。

これにより、FileLoadExceptionの発生を抑え、安定したプラグインシステムを構築できます。

App.config と Web.config の違い

App.configWeb.configは、どちらも.NETアプリケーションの設定ファイルですが、用途や配置場所に違いがあります。

  • App.config
    • 主にデスクトップアプリケーションやコンソールアプリケーションで使用されます
    • プロジェクトのルートにApp.configという名前で配置し、ビルド時に[アプリ名].exe.configとして出力フォルダにコピーされます
    • アプリケーションの起動時に読み込まれ、アセンブリバインディングの設定(bindingRedirectなど)やカスタム設定を記述します
  • Web.config
    • ASP.NETやASP.NET Core(ただしCoreはJSON設定が主流)などのWebアプリケーションで使用されます
    • Webアプリケーションのルートフォルダに配置され、Webサーバー(IISなど)が読み込みます
    • 複数階層に配置可能で、階層ごとに設定を上書き・継承できます
    • セキュリティ設定やHTTPハンドラ、アセンブリバインディングなどWeb特有の設定も含みます

FileLoadExceptionのトラブルシューティングで重要なのは、アセンブリのバージョンリダイレクト設定が正しいファイルに記述されているかどうかです。

デスクトップアプリではApp.config、WebアプリではWeb.configに設定を記述してください。

強名アセンブリが絡む場合

強名アセンブリ(Strong-Named Assembly)は、公開鍵とデジタル署名によって一意に識別されるアセンブリです。

強名アセンブリが絡む場合、FileLoadExceptionの原因として以下の点に注意が必要です。

  • バージョンと公開鍵トークンの一致

強名アセンブリはバージョンだけでなく、公開鍵トークンも一致しなければ読み込めません。

バージョンが合っていても公開鍵トークンが異なると例外が発生します。

  • bindingRedirectの設定

強名アセンブリのバージョンをリダイレクトする場合、App.configWeb.configbindingRedirectに正しい公開鍵トークンを指定する必要があります。

  • GACの影響

強名アセンブリはGACに登録されていることが多く、GACのバージョンと実行時の参照バージョンが異なると読み込みに失敗します。

  • 署名の破損や不一致

アセンブリの署名が破損している、またはビルド時に署名が正しく行われていない場合も例外が発生します。

  • サードパーティ製アセンブリの注意

強名アセンブリでない依存DLLを強名アセンブリから参照すると問題が起こることがあります。

対策としては、強名アセンブリのバージョンと公開鍵トークンを正確に管理し、bindingRedirectを正しく設定することが重要です。

また、GACの状態も確認してください。

Self-contained 配布での対策

Self-contained配布は、.NET Coreや.NET 5以降でアプリケーションとランタイムを一緒に配布する方式です。

この方式では、実行環境に.NETランタイムがインストールされていなくてもアプリケーションが動作しますが、FileLoadExceptionの原因となる依存関係の問題が発生しやすい点に注意が必要です。

Self-contained配布特有の問題

  • 依存DLLの配置場所

ランタイムとアプリケーションが同じフォルダに配置されるため、依存DLLの競合やロード順序の問題が起こることがあります。

  • ネイティブライブラリのロード失敗

RID(Runtime Identifier)に対応したネイティブDLLが正しく含まれていない場合、ロードに失敗します。

  • deps.jsonの不整合

依存関係を記述したdeps.jsonファイルが正しく生成されていないと、アセンブリの解決に失敗します。

対策方法

  • RIDを正しく指定してビルドする

dotnet publishコマンドで-rオプションを使い、ターゲットプラットフォームを明示的に指定します。

dotnet publish -c Release -r win-x64 --self-contained true

  • 依存関係の確認

deps.jsonruntimeconfig.jsonを確認し、必要なDLLやネイティブライブラリが含まれているかチェックします。

  • パスの問題に注意

Self-contained配布では、アプリケーションの実行パスが変わることがあるため、相対パスで依存DLLを指定している場合は注意が必要です。

  • テスト環境での動作確認

実際の配布環境に近い環境で動作確認を行い、依存関係の問題を早期に発見します。

Self-contained配布は利便性が高い反面、依存関係の管理が複雑になるため、ビルド設定や配布ファイルの内容をしっかり確認することがFileLoadException回避のポイントです。

まとめ

この記事では、C#のFileLoadExceptionが発生する主な原因とその解決方法を詳しく解説しました。

アセンブリのバージョン不一致や依存DLL不足、プラットフォームターゲットの不一致、セキュリティ制限など多様な原因を理解し、FusionログやVisual Studioの診断ツール、dotnet CLIを活用した効果的なトラブルシューティング手法を紹介しています。

また、.NET Coreと.NET Frameworkの違いやマルチプラットフォーム環境での注意点、依存性注入コンテナ利用時の落とし穴にも触れ、実践的な対策を網羅しています。

これにより、FileLoadExceptionの原因特定と解決がスムーズに行えるようになります。

関連記事

Back to top button
目次へ