Partilhar via


Sobre System.Runtime.Loader.AssemblyLoadContext

A AssemblyLoadContext classe foi introduzida no .NET Core e não está disponível no .NET Framework. Este artigo complementa a documentação da AssemblyLoadContext API com informações conceituais.

Este artigo é relevante para desenvolvedores que implementam carregamento dinâmico, especialmente desenvolvedores de estrutura de carregamento dinâmico.

O que é o AssemblyLoadContext?

Cada aplicativo .NET 5+ e .NET Core usa implicitamente o AssemblyLoadContext. É o provedor do runtime para localizar e carregar dependências. Sempre que uma dependência é carregada, uma AssemblyLoadContext instância é invocada para localizá-la.

  • AssemblyLoadContext fornece um serviço de localização, carregamento e armazenamento em cache de assemblies gerenciados e outras dependências.
  • Para dar suporte ao carregamento e descarregamento dinâmico de código, ele cria um contexto isolado para carregar código e suas dependências em sua própria AssemblyLoadContext instância.

Regras de controle de versão

Uma única AssemblyLoadContext instância é limitada a carregar exatamente uma única versão de um Assembly por nome de assembly simples. Quando uma referência de assembly é resolvida em relação a uma AssemblyLoadContext instância que já tem um assembly desse nome carregado, a versão solicitada é comparada à versão carregada. A resolução só terá êxito se a versão carregada for igual ou superior à versão solicitada.

Quando precisas de várias instâncias de AssemblyLoadContext?

A restrição de que uma única AssemblyLoadContext instância pode carregar apenas uma versão de um assembly pode se tornar um problema ao carregar módulos de código dinamicamente. Cada módulo é compilado de forma independente, e os módulos podem depender de diferentes versões de um Assemblyarquivo . Isso geralmente é um problema quando módulos diferentes dependem de versões diferentes de uma biblioteca comumente usada.

Para suportar o carregamento dinâmico de código, a AssemblyLoadContext API fornece o carregamento de versões conflitantes de um Assembly no mesmo aplicativo. Cada instância AssemblyLoadContext fornece um dicionário exclusivo que mapeia cada AssemblyName.Name para uma instância Assembly específica.

Ele também fornece um mecanismo conveniente para agrupar dependências relacionadas a um módulo de código para descarga posterior.

A instância AssemblyLoadContext.Default

A AssemblyLoadContext.Default instância é preenchida automaticamente pelo tempo de execução na inicialização. Ele usa sondagem padrão para identificar e localizar todas as dependências estáticas.

Ele resolve os cenários de carregamento de dependência mais comuns.

Dependências dinâmicas

AssemblyLoadContext tem vários eventos e funções virtuais que podem ser substituídas.

A AssemblyLoadContext.Default instância suporta apenas a substituição dos eventos.

Os artigos Managed assembly loading algorithm, Satellite assembly loading algorithm e Unmanaged (native) library loading algorithm referem-se a todos os eventos e funções virtuais disponíveis. Os artigos mostram a posição relativa de cada evento e função nos algoritmos de carregamento. Este artigo não reproduz essa informação.

Esta secção abrange os princípios gerais para os eventos e funções relevantes.

  • Seja repetível. Uma consulta para uma dependência específica deve sempre resultar na mesma resposta. Deve ser retornada exatamente a mesma instância de dependência que foi carregada. Esse requisito é fundamental para a consistência do cache. Para assemblies gerenciados em particular, estamos criando um Assembly cache. A chave de cache é um nome de assembly simples, AssemblyName.Name.
  • Normalmente não atire. Espera-se que essas funções retornem null em vez de serem lançadas quando não for possível encontrar a dependência solicitada. O lançamento encerrará prematuramente a pesquisa e propagará uma exceção ao chamador. O lançamento deve ser restrito a erros inesperados, como uma assemblagem corrompida ou uma condição de falta de memória.
  • Evite a recursão. Lembre-se de que essas funções e manipuladores implementam as regras de carregamento para localizar dependências. Sua implementação não deve chamar APIs que acionam a recursão. O seu código deve normalmente chamar as funções de carregamento de AssemblyLoadContext que exigem um caminho específico ou um argumento de referência de memória.
  • Carregue no AssemblyLoadContext correto. A escolha de onde carregar dependências é específica do aplicativo. A escolha é implementada por esses eventos e funções. Quando seu código chama as funções load-by-path AssemblyLoadContext , chame-as na instância em que você deseja que o código seja carregado. Em algum momento, retornar null e deixar AssemblyLoadContext.Default lidar com a carga pode ser a opção mais simples.
  • Esteja atento às corridas de fios. O carregamento pode ser acionado por vários threads. O AssemblyLoadContext lida com corridas de threads adicionando atomicamente assemblies ao seu cache. A instância do perdedor da corrida é descartada. Na sua lógica de implementação, não adicione lógica extra que não lide com vários threads corretamente.

Como as dependências dinâmicas são isoladas?

Cada AssemblyLoadContext instância representa um escopo exclusivo para Assembly instâncias e Type definições.

Não há isolamento binário entre essas dependências. Eles só ficam isolados por não conseguirem se encontrar pelo nome.

Em cada AssemblyLoadContext:

Dependências compartilhadas

As dependências podem ser facilmente compartilhadas entre AssemblyLoadContext instâncias. O modelo geral é para um AssemblyLoadContext carregar uma dependência. O outro partilha a dependência ao usar uma referência para o conjunto carregado.

Esse compartilhamento é necessário para os assemblies de tempo de execução. Essas assemblagens só podem ser carregadas no AssemblyLoadContext.Default. O mesmo é necessário para estruturas como ASP.NET, WPFou WinForms.

É recomendável que as dependências compartilhadas sejam carregadas no AssemblyLoadContext.Default. Este compartilhamento é o padrão de projeto comum.

O compartilhamento é implementado na codificação da instância personalizada AssemblyLoadContext . AssemblyLoadContext tem vários eventos e funções virtuais que podem ser substituídas. Quando qualquer uma dessas funções retorna uma referência a uma Assembly instância que foi carregada em outra AssemblyLoadContext instância, a Assembly instância é compartilhada. O algoritmo de carga padrão delega o carregamento para AssemblyLoadContext.Default para simplificar o padrão comum de compartilhamento. Para obter mais informações, ver o algoritmo de carregamento de assemblies gerenciados.

Problemas de conversão de tipo

Quando duas AssemblyLoadContext instâncias contêm definições de tipo com o mesmo name, elas não são do mesmo tipo. Eles são do mesmo tipo se e somente se vierem da mesma Assembly instância.

Para complicar, as mensagens de exceção sobre esses tipos incompatíveis podem ser confusas. Os tipos são referidos nas mensagens de exceção por seus nomes de tipo simples. A mensagem de exceção comum neste caso é da forma:

O objeto do tipo 'IsolatedType' não pode ser convertido para o tipo 'IsolatedType'.

Depurar problemas de conversão de tipo

Dado um par de tipos incompatíveis, é importante também saber:

Dado dois objetos a e b, avaliar o seguinte no 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)

Resolver problemas de conversão de tipo

Há dois padrões de design para resolver esses problemas de conversão de tipo.

  1. Use tipos compartilhados comuns. Esse tipo compartilhado pode ser um tipo primitivo de tempo de execução, ou pode envolver a criação de um novo tipo compartilhado num conjunto compartilhado. Muitas vezes, o tipo compartilhado é uma interface definida em um assembly de aplicativo. Para obter mais informações, leia sobre como as dependências são compartilhadas.

  2. Use técnicas de agrupamento para converter de um tipo para outro.