アセンブリ読み込みを解決する

.NET では、アセンブリの読み込みをより細かく制御する必要があるアプリケーションのために、AppDomain.AssemblyResolve イベントが用意されています。 アプリケーションでこのイベントを処理することにより、通常のプローブ パスの外部から読み込みコンテキストにアセンブリを読み込んだり、アセンブリの複数のバージョンから読み込むものを選んだり、動的アセンブリを生成してそれを返したりすることができます。 ここでは、AssemblyResolve イベントの処理について説明します。

Note

リフレクションのみのコンテキストでアセンブリの読み込みを解決する場合は、代わりに AppDomain.ReflectionOnlyAssemblyResolve イベントを使います。

AssemblyResolve イベントのしくみ

AssemblyResolve イベント用のハンドラーを登録すると、ランタイムが名前によるアセンブリへのバインドに失敗すると、常にハンドラーが呼び出されます。 たとえば、ユーザー コードから次のメソッドを呼び出すと、AssemblyResolve イベントが発生する可能性があります。

イベント ハンドラーでの処理

AssemblyResolve イベントのハンドラーは、読み込まれるアセンブリの表示名を、ResolveEventArgs.Name プロパティで受け取ります。 ハンドラーは、アセンブリ名を認識できない場合は null (C#)、Nothing (Visual Basic)、または nullptr (Visual C++) を返します。

ハンドラーは、アセンブリ名を認識すると、要求を満たすアセンブリを読み込んで返すことができます。 シナリオの例をいくつか示します。

  • ハンドラーは、アセンブリのバージョンの場所がわかっている場合は、Assembly.LoadFrom メソッドまたは Assembly.LoadFile メソッドを使ってアセンブリを読み込むことができ、正常に読み込んだ場合はアセンブリを返すことができます。

  • ハンドラーは、バイト配列として格納されているアセンブリのデータベースにアクセスできる場合は、Assembly.Load メソッドのバイト配列を受け取るオーバーロードのいずれかを使って、バイト配列を読み込むことができます。

  • ハンドラーは、動的アセンブリを生成して返すことができます。

注意

ハンドラーを使用して、読み込み元コンテキスト、読み込みコンテキスト、またはコンテキストなしでアセンブリを読み込む必要があります。 ハンドラーで Assembly.ReflectionOnlyLoad メソッドまたは Assembly.ReflectionOnlyLoadFrom メソッドを使用して、リフレクションのみのコンテキストにアセンブリを読み込むと、AssemblyResolve イベントを生成した読み込みの試みは失敗します。

適切なアセンブリを返すのはイベント ハンドラーの役目です。 ハンドラーは、ResolveEventArgs.Name プロパティの値を AssemblyName(String) コンストラクターに渡すことによって、要求されたアセンブリの表示名を解析できます。 .NET Framework 4 以降では、ハンドラーは ResolveEventArgs.RequestingAssembly プロパティを使って、現在の要求が別のアセンブリの依存関係であるかどうかを確認できます。 この情報は、依存関係を満たすアセンブリを特定するのに役立ちます。

イベント ハンドラーは、要求されたバージョンとは異なるバージョンのアセンブリを返すことができます。

ほとんどの場合、ハンドラーによって返されたアセンブリは、ハンドラーがそれを読み込んだコンテキストに関係なく、読み込みコンテキストで表示されます。 たとえば、ハンドラーが Assembly.LoadFrom メソッドを使って読み込み元コンテキストにアセンブリを読み込んだ場合でも、ハンドラーによって返されたアセンブリは、読み込みコンテキストに表示されます。 ただし、次の場合は、ハンドラーによって返されたアセンブリはコンテキストなしで表示されます。

  • ハンドラーがコンテキストなしでアセンブリを読み込んだ場合。

  • ResolveEventArgs.RequestingAssembly プロパティが null ではない場合。

  • 要求元のアセンブリ (つまり、ResolveEventArgs.RequestingAssembly プロパティによって返されるアセンブリ) がコンテキストなしで読み込まれた場合。

コンテキストについて詳しくは、Assembly.LoadFrom(String) メソッドのオーバーロードをご覧ください。

同じアセンブリの複数のバージョンを、同じアプリケーション ドメインに読み込むことができます。 ただし、型の割り当ての問題が発生する可能性があるため、この方法は推奨されません。 「アセンブリの読み込みのベスト プラクティス」をご覧ください。

イベント ハンドラーで行ってはいけないこと

AssemblyResolve イベントの処理に関する基本原則は、認識できないアセンブリを返そうとしてはならない、ということです。 ハンドラーを記述するときは、イベントを発生させる可能性があるアセンブリを知っておく必要があります。 それ以外のアセンブリに対して、ハンドラーは null を返す必要があります。

重要

.NET Framework 4 以降では、AssemblyResolve イベントはサテライト アセンブリに対して発生します。 以前のバージョンの .NET Framework で作成されたイベント ハンドラーが、すべてのアセンブリ読み込み要求を解決しようとしている場合、この変更によって影響を受けます。 認識できないアセンブリを無視するイベント ハンドラーは、この変更による影響を受けません。そのようなハンドラーは null を返し、通常のフォールバック メカニズムに従います。

アセンブリを読み込むとき、イベント ハンドラーは、AssemblyResolve イベントを再帰的に生成する可能性がある AppDomain.Load メソッドまたは Assembly.Load メソッドのオーバー ロードを、使わないようにする必要があります。使った場合、スタック オーバーフローが発生することがあります (このトピックで先に示したリストを参照)。すべてのイベント ハンドラーから返るまで例外はスローされないため、読み込み要求に対して例外処理を提供した場合でも、この問題は発生します。 したがって、次のようなコードでは、MyAssembly が見つからないとスタック オーバーフローが発生します。

using System;
using System.Reflection;

class BadExample
{
    static void Main()
    {
        AppDomain ad = AppDomain.CreateDomain("Test");
        ad.AssemblyResolve += MyHandler;

        try
        {
            object obj = ad.CreateInstanceAndUnwrap(
                "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
                "MyType");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    static Assembly MyHandler(object source, ResolveEventArgs e)
    {
        Console.WriteLine("Resolving {0}", e.Name);
        // DO NOT DO THIS: This causes a StackOverflowException
        return Assembly.Load(e.Name);
    }
}

/* This example produces output similar to the following:

Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
...
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null

Process is terminated due to StackOverflowException.
 */
Imports System.Reflection

Class BadExample

    Shared Sub Main()

        Dim ad As AppDomain = AppDomain.CreateDomain("Test")
        AddHandler ad.AssemblyResolve, AddressOf MyHandler

        Try
            Dim obj As object = ad.CreateInstanceAndUnwrap(
                "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
                "MyType")
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try
    End Sub

    Shared Function MyHandler(ByVal source As Object, _
                              ByVal e As ResolveEventArgs) As Assembly
        Console.WriteLine("Resolving {0}", e.Name)
        // DO NOT DO THIS: This causes a StackOverflowException
        Return Assembly.Load(e.Name)
    End Function
End Class

' This example produces output similar to the following:
'
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'...
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
'
'Process is terminated due to StackOverflowException.
using namespace System;
using namespace System::Reflection;

ref class Example
{
internal:
    static Assembly^ MyHandler(Object^ source, ResolveEventArgs^ e)
    {
        Console::WriteLine("Resolving {0}", e->Name);
        // DO NOT DO THIS: This causes a StackOverflowException
        return Assembly::Load(e->Name);
    }
};

void main()
{
    AppDomain^ ad = AppDomain::CreateDomain("Test");
    ad->AssemblyResolve += gcnew ResolveEventHandler(&Example::MyHandler);

    try
    {
        Object^ obj = ad->CreateInstanceAndUnwrap(
            "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
            "MyType");
    }
    catch (Exception^ ex)
    {
        Console::WriteLine(ex->Message);
    }
}

/* This example produces output similar to the following:

Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
...
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null
Resolving MyAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=null

Process is terminated due to StackOverflowException.
*/

AssemblyResolve を処理する正しい方法

AssemblyResolve イベント ハンドラーからアセンブリを解決すると、このハンドラーによって Assembly.Load または AppDomain.Load のメソッド呼び出しが使用される場合は、最終的に StackOverflowException がスローされます。 AssemblyResolve イベントが生成されないため、代わりに LoadFile または LoadFrom メソッドを使用します。

MyAssembly.dll が実行中のアセンブリの近くの既知の場所にあるとすれば、アセンブリへのパスが指定された Assembly.LoadFile を使用して解決することができます。

using System;
using System.IO;
using System.Reflection;

class CorrectExample
{
    static void Main()
    {
        AppDomain ad = AppDomain.CreateDomain("Test");
        ad.AssemblyResolve += MyHandler;

        try
        {
            object obj = ad.CreateInstanceAndUnwrap(
                "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
                "MyType");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    static Assembly MyHandler(object source, ResolveEventArgs e)
    {
        Console.WriteLine("Resolving {0}", e.Name);

        var path = Path.GetFullPath("../../MyAssembly.dll");
        return Assembly.LoadFile(path);
     }
}
Imports System.IO
Imports System.Reflection

Class CorrectExample

    Shared Sub Main()

        Dim ad As AppDomain = AppDomain.CreateDomain("Test")
        AddHandler ad.AssemblyResolve, AddressOf MyHandler

        Try
            Dim obj As Object = ad.CreateInstanceAndUnwrap(
                "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
                "MyType")
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try
    End Sub

    Shared Function MyHandler(ByVal source As Object,
                              ByVal e As ResolveEventArgs) As Assembly
        Console.WriteLine("Resolving {0}", e.Name)

        Dim fullPath = Path.GetFullPath("../../MyAssembly.dll")
        Return Assembly.LoadFile(fullPath)
    End Function
End Class
using namespace System;
using namespace System::IO;
using namespace System::Reflection;

ref class Example
{
internal:
    static Assembly^ MyHandler(Object^ source, ResolveEventArgs^ e)
    {
        Console::WriteLine("Resolving {0}", e->Name);

        String^ fullPath = Path::GetFullPath("../../MyAssembly.dll");
        return Assembly::LoadFile(fullPath);
    }
};

void main()
{
    AppDomain^ ad = AppDomain::CreateDomain("Test");
    ad->AssemblyResolve += gcnew ResolveEventHandler(&Example::MyHandler);

    try
    {
        Object^ obj = ad->CreateInstanceAndUnwrap(
            "MyAssembly, version=1.2.3.4, culture=neutral, publicKeyToken=null",
            "MyType");
    }
    catch (Exception^ ex)
    {
        Console::WriteLine(ex->Message);
    }
}

関連項目