LoginSignup
3
4

【C#】System.Text.Json における FileLoadException の解決方法

Last updated at Posted at 2023-10-11

はじめに

.Net FrameworkのSystem.Text.Jsonの使用時に、System.IO.FileLoadExceptionが発生した際の解決方法を記載する。FileNotFoundExceptionではないので注意。

動作環境

  • Windows 10
  • Visual Studio 2022
  • .Net Framework 4.7.2

経緯

下記の手順でフォームコントロールライブラリのデバッグをしたところ、当該エラーが発生した。

  1. クラスライブラリプロジェクトにSystem.Text.Json.JsonSerializer.Deserializeを使い、Json文字列をデシリアライズする関数を実装
  2. 同ソリューション内のフォームコントロールライブラリプロジェクトから1のクラスを呼び出す処理を実装
  3. デバッグ機能で2のコントロールの確認を行う
  4. 下記のFileLoadExceptionが発生
例外がスローされました: 'System.IO.FileLoadException' (System.Text.Json.dll の中)
型 'System.IO.FileLoadException' のハンドルされていない例外が System.Text.Json.dll で発生しました
ファイルまたはアセンブリ 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'、またはその依存関係の 1 つが読み込めませんでした。見つかったアセンブリのマニフェスト定義はアセンブリ参照に一致しません。 (HRESULT からの例外:0x80131040)

原因

参照しているSystem.Runtime.CompilerServices.Unsafeのバージョンが古い。
.Net Framework 4.7.2 の Unsafe のバージョンは 6.0.0.0 だが、実行時にバージョン4.0.4.1を参照しているためアセンブリが見つからない趣旨の例外が発生している。

解決方法

アプリケーション系プロジェクトであれば、バインドリダイレクトの自動生成機能でバージョンの差異を解決してくれるので、動作確認用のフォームアプリケーションを別途実装すれば良い。ただ、フォームコントロールライブラリからデバッグ機能を使って検証したい場合はResolveEventHandlerを実装する必要がある。

※別に解決策ありそうだけど、これしか見つからなかった。

ResolveEventHandlerを追加する

System.Runtime.CompilerServices.Unsafeを参照に追加し、フォームコントロールライブラリ内のどこかに以下の処理を実装する。

System.AppDomain.CurrentDomain.AssemblyResolve += new System.ResolveEventHandler((object sender, System.ResolveEventArgs args) => {
  var name = new System.Reflection.AssemblyName(args.Name);
  if (name.Name == "System.Runtime.CompilerServices.Unsafe") {
    return typeof(System.Runtime.CompilerServices.Unsafe).Assembly;
  }
  return null;
});

捕捉

バインドリダイレクトの自動生成がアプリケーション系プロジェクトにしか効果ないことを確認するために、以下のプロジェクトを用意して検証してみた。

  • クラスライブラリプロジェクト(System.Json.Textを使用し、他2クラスから呼び出される)
  • コンソールアプリケーションプロジェクト(クラスライブラリを呼び出して内容を出力)
  • フォームコントロールプロジェクト(クラスライブラリを呼び出して内容を出力)

用意したソースコード

それぞれのプロジェクトで実装したクラスは下記の通り。

ClassLibrary1.Class1.cs
namespace ClassLibrary1 {
  public class Class1 {
    public static Person GetPerson() {
      var person = System.Text.Json.JsonSerializer.Deserialize<Person>(@"{""Name"": ""太郎"", ""Age"": 20}");
      return person;
    }
  }

  public class Person {
    public string Name { get; set; }
    public int Age { get; set; }
  }
}
ConsoleApp1.Program.cs
namespace ConsoleApp1 {
  internal class Program {
    static void Main(string[] args) {
      var person = ClassLibrary1.Class1.GetPerson();
      System.Diagnostics.Debug.WriteLine($"{person.Name} {person.Age}");
    }
  }
}

WindowsFormsContorlLibrary1.UserControl.cs
namespace WindowsFormsControlLibrary1 {
  public partial class UserControl1 : System.Windows.Forms.UserControl {
    public UserControl1() {
      InitializeComponent();
      var person = ClassLibrary1.Class1.GetPerson();
      label1.Text = $"{person.Name} {person.Age}";
    }
  }
}

更に各プロジェクトを右クリック > プロパティ > アプリケーション > バインドリダイレクトの自動生成にチェックが入っていることを確認する(多分デフォルトでチェック入っている)。

実行結果

コンソールアプリケーションをデバッグした場合、デバッグログに正常な出力がされる。

太郎 20

しかし、フォームコントロールライブラリからデバッグした場合、前述の例外発生してしまう。

例外がスローされました: 'System.IO.FileNotFoundException' (System.Text.Json.dll の中)
型 'System.IO.FileNotFoundException' の例外が System.Text.Json.dll で発生しましたが、ユーザー コード内ではハンドルされませんでした
ファイルまたはアセンブリ 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'、またはその依存関係の 1 つが読み込めませんでした。指定されたファイルが見つかりません。

フォームコントロールライブラリで例外が発生する理由

両プロジェクト共にバインドリダイレクトの自動生成が有効になっているのに、なぜフォームコントロールライブラリプロジェクトだけうまくいかないのか?
それを知るにはバインドリダイレクトを理解する必要がある。

バインドリダイレクトとは

ここにあるように、バインドリダイレクトは「1つのアセンブリバージョンを別のバージョンにリダイレクトする」機能である。さらに重要なのは、この設定はアプリケーション構成ファイル(App.config)に定義する点である。例えばコンソールアプリケーションのバインドリダイレクト自動生成を無効にしリビルド後に実行するとFileLoadExceptionが発生し、App.configを以下の通り変更して再度リビルドすると正常に動作することが判る。

ConsoleApp1#App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup> 
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
  </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

アプリケーション構成ファイルに定義するということは・・・

そう、フォームコントロールライブラリプロジェクトはアセンブリのみの出力=実行ファイルを持たない=アプリケーション定義ファイルが存在しないため、バインドリダイレクト機能をプロジェクト単体では使えないのだ。
故に本事象を解決するためには、前述の通り「アプリケーション定義ファイルを持つ別プロジェクトから呼び出す」か「実行時のアセンブリの解決処理に手を加えて無理やり最新バージョンのアセンブリを割り当てて検証」する必要がある。

さいごに

Windowsデスクトップアプリ開発のためC#に触り始めたけど、根本的な事を理解していないので変なところで躓きまくり・・・。これもデバッグの実行時の引数に構成ファイルを追加するとかそもそも古いバージョンをインストールしてしまうとか別の解決策ありそうだけど、ひとまず解決したので先に進んじゃおう。

参考

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4