Auflösen von Assemblyladevorgängen

.NET stellt das Ereignis AppDomain.AssemblyResolve für Anwendungen bereit, die mehr Kontrolle beim Laden von Assemblys benötigen. Durch das Behandeln dieses Ereignisses kann Ihre Anwendung eine Assembly außerhalb der Prüfpfade in einen Kontext laden, vor dem Laden zwischen verschiedenen Assemblyversionen wählen, eine dynamische Assembly ausgeben und diese zurückgeben und so weiter. In diesem Thema erhalten Sie eine Anleitung zum Behandeln des Ereignisses AssemblyResolve.

Hinweis

Zum Auflösen vom Laden einer Assembly in einem reflektionsbezogenen Kontext können Sie stattdessen das Ereignis AppDomain.ReflectionOnlyAssemblyResolve verwenden.

Auslösung des Ereignisses „AssemblyResolve“

Wenn Sie einen Handler für das Ereignis AssemblyResolve registrieren, wird der Handler immer dann aufgerufen, wenn die Runtime eine Assembly nicht anhand deren Namen binden kann. Wenn Sie z.B. die folgende Methode aus Benutzercode aufrufen, kann das Ereignis AssemblyResolve ausgelöst werden:

Welche Aktionen der Ereignishandler ausführt

Der Handler für das Ereignis AssemblyResolve erhält den Anzeigenamen der zu ladenden Assembly in der Eigenschaft ResolveEventArgs.Name. Wenn der Handler den Assemblynamen nicht erkennt, gibt er null (C#), Nothing (Visual Basic) oder nullptr (Visual C++) zurück.

Wenn der Handler den Assemblynamen erkennt, kann er eine Assembly laden und zurückgeben, die der Anforderung entspricht. In der folgenden Liste werden einige Beispielszenarios beschrieben.

  • Wenn der Handler den Speicherort einer Version der Assembly kennt, kann er die Assembly mit den Methoden Assembly.LoadFrom oder Assembly.LoadFile laden und bei Erfolg die geladene Assembly zurückgeben.

  • Wenn der Handler auf die Datenbank von Assemblys zugreifen kann, die als Bytearrays gespeichert sind, kann er das Bytearray mit den Methodenüberladungen Assembly.Load laden, die Bytearrays akzeptieren.

  • Der Handler kann eine dynamische Assembly generieren und diese zurückgeben.

Hinweis

Der Handler muss die Assembly in den LoadFrom-Kontext, in den Ladekontext oder ohne Kontext laden. Wenn der Handler die Assembly mithilfe der Assembly.ReflectionOnlyLoad- oder der Assembly.ReflectionOnlyLoadFrom-Methode in den Kontext „Reflektionsbezogener Ladekontext“ lädt, schlägt der Ladeversuch fehl, der das AssemblyResolve-Ereignis ausgelöst hat.

Der Ereignishandler ist dafür verantwortlich, eine passende Assembly zurückzugeben. Der Handler kann den Anzeigename der angeforderten Assembly analysieren, indem er den Wert der ResolveEventArgs.Name-Eigenschaft an den AssemblyName(String)-Konstruktor übergibt. Ab .NET Framework 4 kann der Handler die ResolveEventArgs.RequestingAssembly-Eigenschaft verwenden, um zu bestimmen, ob die aktuelle Anforderung eine Abhängigkeit von einer anderen Assembly darstellt. Diese Information kann dabei helfen, eine Assembly zu identifizieren die die Abhängigkeit erfüllt.

Der Ereignishandler kann eine Version der Assembly zurückgeben, die sich von der angeforderten Version unterscheidet.

In den meisten Fällen befindet sich die vom Handler zurückgegebene Assembly in einem load-Kontext, unabhängig vom Kontext, in der der Handler sie lädt. Wenn der Handler z.B. die Assembly.LoadFrom-Methode verwendet, um eine Assembly in einen load-from-Kontext zu laden, befindet sich die Assembly im load-Kontext, wenn der Handler sie zurückgibt. Im folgenden Kontext befindet sich die Assembly jedoch in keinem Kontext, wenn sie vom Handler zurückgegeben wird:

Weitere Informationen zu Kontexten finden Sie in der Methodenüberladung Assembly.LoadFrom(String).

Mehrere Versionen derselben Assembly können in die gleiche Anwendungsdomäne geladen werden. Diese Vorgehensweise wird jedoch nicht empfohlen, da sie zu Problemen bei der Typzuweisung führen kann. Weitere Informationen finden Sie unter Best Practices für das Laden von Assemblys.

Welche Aktionen der Ereignishandler nicht ausführen sollte

Die erste Regeln beim Behandeln des Ereignis AssemblyResolve ist, dass Sie nie versuchen sollten, eine Assembly zurückzugeben, die Sie nicht erkennen. Wenn Sie den Handler schreiben, sollten Sie wissen, welche Assemblys das Ereignis auslösen können. Ihr Handler sollte für andere Assemblys NULL zurückgeben.

Wichtig

Ab .NET Framework 4 wird das Ereignis AssemblyResolve für Satellitenassemblys ausgelöst. Diese Änderung betrifft Ereignishandler, die für eine frühere Version von .NET Framework geschrieben wurden, wenn diese versuchen, alle Ladeanforderungen von Assemblys aufzulösen. Ereignishandler, die Assemblys ignorieren, die sie nicht erkennen, sind von dieser Änderung nicht betroffen: Sie geben null zurück und folgen dann den üblichen Fallbackmechanismen.

Beim Laden einer Assembly darf der Ereignishandler nicht die Methodenüberladungen AppDomain.Load oder Assembly.Load verwendet, die dazu führen können, dass das Ereignis AssemblyResolve rekursiv ausgelöst wird, da dies zu einem Stapelüberlauf führen kann. (Weitere Informationen finden Sie in der weiter oben in diesem Thema angegebenen Liste.) Dies geschieht auch, wenn Sie eine Ausnahmebehandlung für die Ladeanforderung bereitstellen, da keine Ausnahme ausgelöst wird, bis alle Ereignishandler etwas zurückgegeben haben. Deshalb führt der folgende Code zu einem Stapelüberlauf, wenn MyAssembly nicht gefunden werden kann:

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.
*/

Die richtige Behandlung von AssemblyResolve

Beim Auflösen von Assemblys aus dem AssemblyResolve-Ereignishandler wird schließlich eine StackOverflowException ausgelöst, wenn der Handler die Methodenaufrufe Assembly.Load oder AppDomain.Load verwendet. Verwenden Sie stattdessen die Methode LoadFile oder LoadFrom, da sie das AssemblyResolve-Ereignis nicht auslösen.

Stellen Sie sich vor, dass MyAssembly.dll sich in der Nähe der ausführenden Assembly an einem bekannten Speicherort befindet. Sie kann mithilfe von Assembly.LoadFile aufgelöst werden, vorausgesetzt der Pfad zur Assembly ist angegeben.

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);
    }
}

Siehe auch