AssemblyLoadContext クラスは .NET Core で導入され、.NET Framework では使用できません。 この記事では、 AssemblyLoadContext API のドキュメントと概念情報を補足します。
この記事は、動的読み込みを実装する開発者 (特に動的読み込みフレームワーク開発者) に関連します。
AssemblyLoadContext とは
すべての .NET 5 以降および .NET Core アプリケーションでは、 AssemblyLoadContextが暗黙的に使用されます。 依存関係を検索して読み込むためのランタイムのプロバイダーです。 依存関係が読み込まれるたびに、 AssemblyLoadContext インスタンスが呼び出されて見つけることができます。
- AssemblyLoadContext は、マネージド アセンブリとその他の依存関係を検索、読み込み、キャッシュするサービスを提供します。
- 動的なコードの読み込みとアンロードをサポートするために、コードとその依存関係を独自の AssemblyLoadContext インスタンスに読み込むための分離されたコンテキストが作成されます。
バージョン管理ルール
1 つのAssemblyLoadContext インスタンスは、Assemblyごとに 1 つのバージョンのの読み込みに制限されます。 その名前のアセンブリが既に読み込まれている AssemblyLoadContext インスタンスに対してアセンブリ参照が解決されると、要求されたバージョンが読み込まれたバージョンと比較されます。 読み込まれたバージョンが要求されたバージョン以上の場合にのみ、解決が成功します。
複数の AssemblyLoadContext インスタンスが必要な場合
コード モジュールを動的に読み込むときに、1 つの AssemblyLoadContext インスタンスがアセンブリの 1 つのバージョンのみを読み込むことができるという制限が問題になる可能性があります。 各モジュールは個別にコンパイルされ、モジュールは Assemblyのバージョンによって異なる場合があります。 これは多くの場合、一般的に使用されるライブラリのバージョンによって異なるモジュールが異なる場合に問題になります。
コードの動的な読み込みをサポートするために、 AssemblyLoadContext API は、競合するバージョンの Assembly を同じアプリケーションに読み込むための機能を提供します。 各 AssemblyLoadContext インスタンスには、各 AssemblyName.Name を特定の Assembly インスタンスにマップする一意のディクショナリが用意されています。
また、後でアンロードするために、コード モジュールに関連する依存関係をグループ化するための便利なメカニズムも提供します。
AssemblyLoadContext.Default インスタンス
AssemblyLoadContext.Default インスタンスは、起動時にランタイムによって自動的に設定されます。 既定の プローブを 使用して、すべての静的依存関係を見つけて検索します。
これは、最も一般的な依存関係の読み込みシナリオを解決します。
動的依存関係
AssemblyLoadContext には、オーバーライドできるさまざまなイベントと仮想関数があります。
AssemblyLoadContext.Default インスタンスでは、イベントのオーバーライドのみがサポートされます。
マネージド アセンブリ読み込みアルゴリズム、サテライト アセンブリ読み込みアルゴリズム、アンマネージド (ネイティブ) ライブラリ読み込みアルゴリズムに関する記事では、使用可能なすべてのイベントと仮想関数を参照します。 この記事では、読み込みアルゴリズムにおける各イベントと関数の相対位置を示します。 この記事では、その情報は再現されません。
このセクションでは、関連するイベントと関数の一般的な原則について説明します。
- 繰り返し可能にする。 特定の依存関係に対するクエリでは、常に同じ応答が返される必要があります。 読み込まれた同じ依存関係インスタンスを返す必要があります。 この要件は、キャッシュ整合性の基本です。 特にマネージド アセンブリの場合、 Assembly キャッシュを作成しています。 キャッシュ キーは、単純なアセンブリ名 ( AssemblyName.Name) です。
-
通常はスローしない. これらの関数は、要求された依存関係を見つけることができないときにスローするのではなく、
nullを返すことが期待されます。 スローすると、検索が途中で終了し、例外が呼び出し元に伝達されます。 スローは、破損したアセンブリやメモリ不足の状態など、予期しないエラーに限定する必要があります。 - 再帰は避けてください。 これらの関数とハンドラーは、依存関係を検索するための読み込み規則を実装していることに注意してください。 実装では、再帰をトリガーする API を呼び出さないでください。 コードでは通常、特定のパスまたはメモリ参照引数を必要とする AssemblyLoadContext 読み込み関数を呼び出す必要があります。
-
適切な AssemblyLoadContext に読み込みます。 依存関係を読み込む場所の選択は、アプリケーション固有です。 選択は、これらのイベントと関数によって実装されます。 コードで AssemblyLoadContext load-by-path 関数を呼び出すと、コードを読み込むインスタンスでそれらを呼び出します。 場合によっては、
nullを返し、 AssemblyLoadContext.Default が負荷を処理できるようにすることが最も簡単なオプションである可能性があります。 - スレッド の競合に注意してください。 読み込みをトリガーできるのは、複数のスレッドです。 AssemblyLoadContext は、アセンブリをキャッシュにアトミックに追加することによってスレッド 競合を処理します。 競合の敗者のインスタンスは破棄されます。 実装ロジックでは、複数のスレッドを適切に処理しないロジックを追加しないでください。
動的依存関係はどのように分離されますか?
各 AssemblyLoadContext インスタンスは、 Assembly インスタンスと Type 定義の一意のスコープを表します。
これらの依存関係間にバイナリ分離はありません。 名前によって互いを検索しないことによってのみ分離されています。
各 AssemblyLoadContextで次の手順を実行します。
- AssemblyName.Name は、別の Assembly インスタンスを参照する場合があります。
-
Type.GetType は、同じ型
nameに対して異なる型インスタンスを返す場合があります。
共有依存関係
依存関係は、 AssemblyLoadContext インスタンス間で簡単に共有できます。 一般的なモデルは、1 つの AssemblyLoadContext が依存関係を読み込むためのモデルです。 もう 1 つでは、読み込まれたアセンブリへの参照を使用して依存関係を共有します。
この共有は、ランタイム アセンブリに必要です。 これらのアセンブリは、 AssemblyLoadContext.Defaultにのみ読み込むことができます。
ASP.NET、WPF、WinFormsなどのフレームワークにも同じ要件があります。
共有依存関係を AssemblyLoadContext.Defaultに読み込むすることをお勧めします。 この共有は、一般的な設計パターンです。
共有は、カスタム AssemblyLoadContext インスタンスのコーディングで実装されます。 AssemblyLoadContext には、オーバーライドできるさまざまなイベントと仮想関数があります。 これらの関数のいずれかが、別のAssembly インスタンスに読み込まれたAssemblyLoadContext インスタンスへの参照を返すと、Assembly インスタンスは共有されます。 標準的なロードアルゴリズムは、共通の共有パターンを簡略化するために、AssemblyLoadContext.Default に委託して読み込みます。 詳細については、「 マネージド アセンブリ読み込みアルゴリズム」を参照してください。
型変換の問題
2 つの AssemblyLoadContext インスタンスに同じ nameを持つ型定義が含まれている場合、それらは同じ型ではありません。 同じ Assembly インスタンスから取得した場合にのみ、同じ型になります。
問題を複雑にするために、これらの型の不一致に関する例外メッセージが混乱する可能性があります。 これらの型は、単純型名によって例外メッセージ内で参照されます。 この場合の一般的な例外メッセージの形式は次のとおりです。
型 'IsolatedType' のオブジェクトを型 'IsolatedType' に変換できません。
型変換の問題をデバッグする
一致しない型のペアを考えると、次の点も把握することが重要です。
- 各型の Type.Assembly。
- 各型の AssemblyLoadContext。 AssemblyLoadContext.GetLoadContext(Assembly) 関数を使用して取得できます。
aとbの 2 つのオブジェクトがある場合、デバッガーで次を評価すると役立ちます。
// In debugger look at each assembly's instance, Location, and FullName
a.GetType().Assembly
b.GetType().Assembly
// In debugger look at each AssemblyLoadContext's instance and name
System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(a.GetType().Assembly)
System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(b.GetType().Assembly)
型変換の問題を解決する
これらの型変換の問題を解決するための 2 つの設計パターンがあります。
一般的な共有型を使用します。 この共有型は、プリミティブ ランタイム型にすることも、共有アセンブリに新しい共有型を作成することも含めることができます。 多くの場合、共有型はアプリケーション アセンブリで定義されている インターフェイス です。 詳細については、 依存関係の共有方法に関する記事を参照してください。
マーシャリング手法を使用して、ある型から別の型に変換します。
静的メンバーにアクセスする
カスタム AssemblyLoadContext に読み込まれる型は、他のコンテキストの型から分離されるため、リフレクションを使用してコンテキストの外部から静的メンバーにアクセスする必要があります。
たとえば、動的に読み込まれたアセンブリ内の次の静的クラスを考えてみましょう。
namespace MyPlugin;
public static class Paths
{
public static DirectoryInfo RootIO { get; private set; }
}
PropertyInfo.GetValueを使用してプロパティ値を読み取る。 静的メンバーにはインスタンスが必要ないため、 null を最初の引数として渡します。 完全修飾型名 (名前空間を含む) を Assembly.GetType(String)に渡します。
// Get the type from the loaded assembly using the fully qualified name
Type pathsType = loadedAssembly.GetType("MyPlugin.Paths")
?? throw new InvalidOperationException("Type 'MyPlugin.Paths' not found in loaded assembly.");
// Use PropertyInfo to access a static property
PropertyInfo rootIoProperty = pathsType.GetProperty("RootIO")
?? throw new InvalidOperationException("Property 'RootIO' was not found on type 'MyPlugin.Paths'.");
DirectoryInfo rootIo = (DirectoryInfo)rootIoProperty.GetValue(null);
または、C# は、プロパティ アクセサーを get_ および set_ プレフィックスを持つメソッドにコンパイルします。 これらのアクセサー メソッドは、 Type.GetMethodを使用して直接呼び出すことができます。 ただし、 Type.GetMethod(String) はパブリック メソッドのみを返します。 アクセサーがパブリックでない場合 (例の private set など)、 BindingFlagsを受け入れるオーバーロードを使用する必要があります。
// Public getter — no BindingFlags needed
MethodInfo getRootIo = pathsType.GetMethod("get_RootIO")
?? throw new InvalidOperationException("Accessor method 'get_RootIO' was not found on type 'MyPlugin.Paths'.");
DirectoryInfo rootIo = (DirectoryInfo)getRootIo.Invoke(null, null);
// Non-public setter — must use BindingFlags
MethodInfo setRootIo = pathsType.GetMethod(
"set_RootIO",
BindingFlags.Static | BindingFlags.NonPublic)
?? throw new InvalidOperationException("Accessor method 'set_RootIO' was not found on type 'MyPlugin.Paths'.");
setRootIo.Invoke(null, new object[] { newValue });
同じパターンは、 FieldInfo.GetValue と FieldInfo.SetValueを介してアクセスできる静的フィールドと、 MethodBase.Invokeで呼び出す静的メソッドにも適用されます。
注
読み込まれたアセンブリで型が定義されている値を取得すると、呼び出し元のコンテキストで型変換の問題が発生する可能性があります。 詳細については、「 型変換の問題」を参照してください。
.NET