Resolución de cargas de ensamblado

.NET proporciona el evento AppDomain.AssemblyResolve para aplicaciones que requieren un mayor control sobre la carga de ensamblados. Al controlar este evento, la aplicación puede cargar un ensamblado en el contexto de carga desde fuera de las rutas de acceso de sondeo normales, seleccionar qué versión de ensamblado cargar, emitir un ensamblado dinámico y devolverlo, etc. En este tema se proporcionan instrucciones para controlar el evento AssemblyResolve.

Nota

Para resolver cargas de ensamblado en el contexto de solo reflexión, use en su lugar el evento AppDomain.ReflectionOnlyAssemblyResolve.

Cómo funciona el evento AssemblyResolve

Al registrar un controlador para el evento AssemblyResolve, se invoca el controlador si se produce un error cuando el tiempo de ejecución enlaza a un ensamblado por nombre. Por ejemplo, si se llama a los métodos siguientes desde el código de usuario, puede producirse el evento AssemblyResolve:

Qué hace el controlador de eventos

El controlador del evento AssemblyResolve recibe el nombre para mostrar del ensamblado que se va a cargar, en la propiedad ResolveEventArgs.Name. Si el controlador no reconoce el nombre del ensamblado, devuelve null (C#), Nothing (Visual Basic) o nullptr (Visual C++).

Si el controlador reconoce el nombre del ensamblado, puede cargar y devolver un ensamblado que cumpla la solicitud. En la lista siguiente se describen algunos escenarios de ejemplo.

  • Si el controlador conoce la ubicación de una versión del ensamblado, puede cargar el ensamblado mediante el método Assembly.LoadFrom o Assembly.LoadFile y devolver el ensamblado cargado si se realiza correctamente.

  • Si el controlador tiene acceso a una base de datos de los ensamblados almacenados como matrices de bytes, puede cargar una matriz de bytes mediante una de las sobrecargas del método Assembly.Load que toman una matriz de bytes.

  • El controlador puede generar un ensamblado dinámico y devolverlo.

Nota

El controlador debe cargar el ensamblado en el contexto de origen de carga, en el contexto de carga o sin contexto. Si el controlador carga el ensamblado en el contexto de solo reflexión mediante Assembly.ReflectionOnlyLoad o el método Assembly.ReflectionOnlyLoadFrom, el intento de carga que ha generado el evento AssemblyResolve sufre un error.

El controlador de eventos se encarga de devolver un ensamblado adecuado. El controlador puede analizar el nombre para mostrar del ensamblado solicitado. Para ello, pasa el valor de propiedad ResolveEventArgs.Name al constructor AssemblyName(String). A partir de .NET Framework 4, el controlador puede usar la propiedad ResolveEventArgs.RequestingAssembly para determinar si la solicitud actual es una dependencia de otro ensamblado. Esta información puede ayudar a identificar un ensamblado que cumplirá la dependencia.

El controlador de eventos puede devolver una versión del ensamblado diferente de la versión solicitada.

En la mayoría de los casos, el ensamblado que el controlador devuelve aparece en el contexto de carga, independientemente del contexto en el que lo carga el controlador. Por ejemplo, si el controlador usa el método Assembly.LoadFrom para cargar un ensamblado en el contexto de origen de carga, el ensamblado aparece en el contexto de carga cuando el controlador lo devuelve. Pero en el caso siguiente, el ensamblado aparece sin contexto cuando el controlador lo devuelve:

Para obtener información sobre los contextos, vea la sobrecarga del método Assembly.LoadFrom(String).

Es posible cargar varias versiones del mismo ensamblado en el mismo dominio de aplicación, pero esta práctica no se recomienda porque puede provocar problemas de asignación de tipos. Vea Procedimientos recomendados para cargar ensamblados.

Qué no debe hacer el controlador de eventos

La regla principal que debe observar para controlar el evento AssemblyResolve es que no debe intentar devolver un ensamblado que no reconozca. Cuando se escribe el controlador, debe saber qué ensamblados pueden hacer que se produzca el evento. El controlador debe devolver NULL para otros ensamblados.

Importante

A partir de .NET Framework 4, se genera el evento AssemblyResolve para los ensamblados satélite. Este cambio afecta a los controladores de eventos escritos para versiones anteriores de .NET Framework si intentan resolver todas las solicitudes de carga del ensamblado. Este cambio no afecta a los controladores de eventos que omiten los ensamblados que no reconocen: devuelven null y se siguen los mecanismos normales de reserva.

Al cargar un ensamblado, el controlador de eventos no debe usar las sobrecargas del método AppDomain.Load o Assembly.Load que pueden hacer que se produzca el evento AssemblyResolve de forma recursiva, ya que esto puede provocar un desbordamiento de pila. (Vea la lista proporcionada anteriormente en este tema). Esto ocurre incluso si proporciona el control de excepciones para la solicitud de carga, porque no se produce ninguna excepción hasta que se hayan devuelto todos los controladores de eventos. Así pues, el código siguiente produce un desbordamiento de pila si no se encuentra 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.
*/

Manera correcta de controlar AssemblyResolve

Al resolver ensamblados desde el controlador de eventos AssemblyResolve, al final se inicia una excepción StackOverflowException si el controlador usa las llamadas al método Assembly.Load o AppDomain.Load. En su lugar, use métodos LoadFile o LoadFrom, ya que no generan el evento AssemblyResolve.

Imagine que MyAssembly.dll se encuentra cerca del ensamblado en ejecución, en una ubicación conocida, se puede resolver mediante Assembly.LoadFile dada la ruta de acceso al ensamblado.

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

Vea también