Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
La AssemblyLoadContext clase se introdujo en .NET Core y no está disponible en .NET Framework. En este artículo se complementa la documentación de la AssemblyLoadContext API con información conceptual.
Este artículo es relevante para los desarrolladores que implementan la carga dinámica, especialmente los desarrolladores de marcos de carga dinámica.
¿Qué es AssemblyLoadContext?
Cada aplicación de .NET 5+ y .NET Core usa AssemblyLoadContextimplícitamente . Es el proveedor del entorno de ejecución para buscar y cargar dependencias. Cada vez que se carga una dependencia, se invoca una AssemblyLoadContext instancia para localizarla.
- AssemblyLoadContext proporciona un servicio de búsqueda, carga y almacenamiento en caché de ensamblados administrados y otras dependencias.
- Para admitir la carga y descarga de código dinámico, crea un contexto aislado para cargar código y sus dependencias en su propia AssemblyLoadContext instancia.
Reglas de control de versiones
Una única instancia de AssemblyLoadContext se limita a cargar exactamente una versión de un elemento Assembly por nombre de ensamblado simple. Cuando se resuelve una referencia de ensamblado en una AssemblyLoadContext instancia que ya tiene un ensamblado de ese nombre cargado, la versión solicitada se compara con la versión cargada. La resolución solo se realizará correctamente si la versión cargada es igual o superior a la versión solicitada.
¿Cuándo necesita varias instancias de AssemblyLoadContext?
La restricción de que una sola AssemblyLoadContext instancia puede cargar solo una versión de un ensamblado puede convertirse en un problema al cargar los módulos de código dinámicamente. Cada módulo se compila de forma independiente y los módulos pueden depender de diferentes versiones de una Assembly. Esto suele ser un problema cuando los distintos módulos dependen de versiones diferentes de una biblioteca usada habitualmente.
Para admitir la carga dinámica de código, la AssemblyLoadContext API permite cargar versiones en conflicto de un Assembly en la misma aplicación. Cada AssemblyLoadContext instancia proporciona un diccionario único que asigna a cada AssemblyName.Name a una instancia Assembly específica.
También proporciona un mecanismo práctico para agrupar dependencias relacionadas con un módulo de código para descargarlas más adelante.
La instancia predeterminada de AssemblyLoadContext
La instancia AssemblyLoadContext.Default es poblada automáticamente por el tiempo de ejecución al inicio. Usa el sondeo predeterminado para localizar y encontrar todas las dependencias estáticas.
Resuelve los escenarios de carga de dependencias más comunes.
Dependencias dinámicas
AssemblyLoadContext tiene varios eventos y funciones virtuales que se pueden invalidar.
La AssemblyLoadContext.Default instancia solo admite la invalidación de los eventos.
Los artículos Algoritmo de carga de ensamblados administrados, Algoritmo de carga de ensamblado satélite, y Algoritmo de carga de biblioteca no administrada (nativa) se refieren a todos los eventos y funciones virtuales disponibles. Los artículos muestran la posición relativa de cada evento y función en los algoritmos de carga. Este artículo no reproduce esa información.
En esta sección se tratan los principios generales de los eventos y funciones pertinentes.
- Sea repetible. Una consulta para una dependencia específica siempre debe dar lugar a la misma respuesta. Debe devolverse la misma instancia de dependencia cargada. Este requisito es fundamental para la coherencia de la caché. En el caso de los ensamblados administrados en concreto, vamos a crear una Assembly memoria caché. La clave de caché es un nombre de ensamblado simple, AssemblyName.Name.
-
Normalmente no se inician. Se espera que estas funciones devuelvan
null, en lugar de iniciarse, cuando no se pueda encontrar la dependencia solicitada. La generación finalizará prematuramente la búsqueda y propagará una excepción al autor de la llamada. El lanzamiento debe estar restringido a errores inesperados, como un ensamblaje dañado o una condición de memoria insuficiente. - Evite la recursividad. Tenga en cuenta que estas funciones y controladores implementan las reglas de carga para buscar dependencias. La implementación no debe llamar a las API que desencadenan la recursividad. El código debe, por lo general, llamar a funciones de carga de AssemblyLoadContext que requieren una ruta de acceso específica o un argumento de referencia de memoria.
-
Cargar en el elemento AssemblyLoadContext correcto. La elección de dónde cargar las dependencias es específica de la aplicación. Estas funciones y eventos implementan la elección. Cuando el código llama a las funciones de carga por ruta de AssemblyLoadContext, hágalo en la instancia en la que desea cargar el código. En algún momento, devolver
nully dejar que AssemblyLoadContext.Default maneje la carga puede ser la opción más sencilla. - Tenga en cuenta las carreras de subprocesos. La carga la pueden desencadenar varios subprocesos. AssemblyLoadContext controla las carreras de subprocesos mediante la adición atómica de ensamblados a su caché. La instancia del que pierde la carrera se descarta. En la lógica de implementación, no agregue lógica adicional que no controle varios subprocesos correctamente.
¿Cómo están aisladas las dependencias dinámicas?
Cada AssemblyLoadContext instancia representa un ámbito único para Assembly instancias y Type definiciones.
No hay aislamiento binario entre estas dependencias. Solo están aisladas por no encontrarse entre sí por nombre.
En cada AssemblyLoadContext:
- AssemblyName.Name puede hacer referencia a una instancia diferente Assembly .
-
Type.GetType puede devolver una instancia de tipo diferente para el mismo tipo
name.
Dependencias compartidas
Las dependencias se pueden compartir fácilmente entre AssemblyLoadContext instancias. El modelo general es que cada AssemblyLoadContext cargue una dependencia. El otro comparte la dependencia mediante el uso de una referencia en el ensamblado cargado.
Este uso compartido es necesario para los ensamblados en runtime. Estos ensamblados solo se pueden cargar en AssemblyLoadContext.Default. Lo mismo es necesario para marcos como ASP.NET, WPFo WinForms.
Se recomienda cargar las dependencias compartidas en AssemblyLoadContext.Default. Este uso compartido es el modelo de diseño común.
El uso compartido se implementa en la codificación de la instancia personalizada AssemblyLoadContext . AssemblyLoadContext tiene varios eventos y funciones virtuales que se pueden invalidar. La instancia Assembly se comparte cuando cualquiera de estas funciones devuelve una referencia a una instancia AssemblyLoadContext cargada en otra instancia Assembly. El algoritmo de carga estándar se aplaza a AssemblyLoadContext.Default para cargar con el fin de simplificar el patrón común de uso compartido. Para obtener más información, consulte Algoritmo de carga de ensamblados administrados.
Incidencias de conversión de tipos
Cuando dos AssemblyLoadContext instancias contienen definiciones de tipo con el mismo name, no son del mismo tipo. Son del mismo tipo si y solo si proceden de la misma Assembly instancia.
Para complicar las cosas, los mensajes de excepción sobre estos tipos incompatibles pueden ser confusos. Los tipos se conocen en los mensajes de excepción por sus nombres de tipo simples. El mensaje de excepción común en este caso tiene el formato :
El objeto de tipo "IsolatedType" no se puede convertir al tipo "IsolatedType".
Depuración de incidencias de conversión de tipos
Dado un par de tipos no coincidentes, es importante conocer también lo siguiente:
- El elemento Type.Assembly de cada tipo.
- El elemento AssemblyLoadContext de cada tipo., que se puede obtener a través de la función AssemblyLoadContext.GetLoadContext(Assembly).
Dados dos objetos a y b, evaluar lo siguiente en el depurador será útil:
// 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)
Resolución de incidencias de conversión de tipos
Hay dos patrones de diseño para resolver estos problemas de conversión de tipos.
Use tipos compartidos comunes. Este tipo compartido puede ser un tipo en tiempo de ejecución primitivo o puede implicar la creación de un nuevo tipo compartido en un ensamblado compartido. A menudo, el tipo compartido es una interfaz definida en un ensamblado de aplicación. Para obtener más información, lea cómo se comparten las dependencias.
Use técnicas de serialización para realizar la conversión de un tipo a otro.