Compartilhar via


Práticas recomendadas para carregamento de assemblies

Observação

Este artigo é específico do .NET Framework. Ele não se aplica a implementações mais recentes do .NET, incluindo o .NET 6 e versões posteriores.

Este artigo discute maneiras de evitar problemas de identidade de tipo que podem levar a InvalidCastException, MissingMethodExceptione outros erros. O artigo discute as seguintes recomendações:

A primeira recomendação, entender as vantagens e desvantagens dos contextos de carga, fornece informações em segundo plano para as outras recomendações, pois todas elas dependem de um conhecimento dos contextos de carga.

Entender as vantagens e desvantagens dos contextos de carga

Em um domínio de aplicativo, os assemblies podem ser carregados em um dos três contextos ou podem ser carregados sem contexto:

  • O contexto de carregamento padrão contém assemblies localizados investigando o cache de assembly global, o armazenamento do assembly do host se o runtime é hospedado (por exemplo, no SQL Server) e o ApplicationBase e o PrivateBinPath do domínio do aplicativo. A maioria das sobrecargas do método Load carrega conjuntos nesse contexto.

  • O contexto de origem de carregamento contém assemblies carregados a partir de locais que não são pesquisados pelo carregador. Por exemplo, os plugins podem ser instalados em um diretório que não está na pasta de instalação do aplicativo. Assembly.LoadFrom, AppDomain.CreateInstanceFrome AppDomain.ExecuteAssembly são exemplos de métodos que são carregados por caminho.

  • O contexto somente para reflexão contém assemblies carregados com os métodos ReflectionOnlyLoad e ReflectionOnlyLoadFrom. O código neste contexto não pode ser executado, portanto, não é discutido aqui. Para obter mais informações, consulte Como carregar assemblies no contexto de somente para reflexão.

  • Se você gerou um assembly dinâmico transitório usando a emissão de reflexo, o assembly não está em nenhum contexto. Além disso, a maioria dos assemblies carregados usando o método LoadFile são carregados sem contexto e os assemblies carregados de matrizes de bytes são carregados sem contexto, a menos que sua identidade (depois que a política é aplicada) estabeleça que estão no cache de assembly global.

Os contextos de execução têm vantagens e desvantagens, conforme discutido nas seções a seguir.

Contexto de carga padrão

Quando os assemblies são carregados no contexto de carga padrão, suas dependências são carregadas automaticamente. As dependências carregadas no contexto de carregamento padrão são encontradas automaticamente para assemblies no contexto de carregamento padrão ou no contexto de origem de carregamento. O carregamento por identidade do assembly aumenta a estabilidade de aplicativos ao garantir que versões desconhecidas do aplicativo não sejam usadas (consulte a seção Evite associações em nomes de assembly parciais).

O uso do contexto de carga padrão tem as seguintes desvantagens:

  • As dependências carregadas em outros contextos não estão disponíveis.

  • Você não pode carregar assemblies de locais fora do caminho de sondagem no contexto de carga padrão.

Contexto de origem de carregamento

O contexto de origem de carregamento permite que você carregue um assembly de um caminho que não está no caminho do aplicativo e, portanto, não está incluído na investigação. Ele permite que as dependências sejam localizadas e carregadas a partir desse caminho, pois é o contexto que mantém as informações do caminho. Além disso, os assemblies nesse contexto podem usar dependências carregadas no contexto de carregamento padrão.

Carregar assemblies usando o método Assembly.LoadFrom ou um dos outros métodos que carregam por caminho tem as seguintes desvantagens:

  • Se um assembly com a mesma identidade já estiver carregado no contexto de carregamento, LoadFrom retornará o assembly carregado mesmo se um caminho diferente tiver sido especificado.

  • Se um assembly for carregado com LoadFrom e, posteriormente, um assembly no contexto de carga padrão tentar carregar o mesmo assembly por nome de exibição, a tentativa de carga falhará. Isso pode ocorrer quando um assembly é desserializado.

  • Se um assembly é carregado com LoadFrom e o caminho de investigação inclui um assembly com a mesma identidade, mas em um local diferente, um InvalidCastException, MissingMethodException ou outro comportamento inesperado pode ocorrer.

  • LoadFrom exige FileIOPermissionAccess.Read e FileIOPermissionAccess.PathDiscovery, ou WebPermission, no caminho especificado.

  • Se existir uma imagem nativa para o assembly, ela não será usada.

  • O assembly não pode ser carregado como de domínio neutro.

  • Nas versões do .NET Framework 1.0 e 1.1, a política não é aplicada.

Sem contexto

O carregamento sem contexto é a única opção para assemblies transitórios que são gerados com a emissão de reflexão. Carregar sem contexto é a única maneira de carregar vários assemblies que têm a mesma identidade em um domínio de aplicativo. O custo da investigação é evitado.

Os assemblies que são carregados de matrizes de bytes são carregados sem contexto, a menos que a identidade do assembly, que é estabelecida quando a política é aplicada, corresponda à identidade de um assembly no cache de assembly global. Nesse caso, o assembly é carregado do cache de assembly global.

Carregar conjuntos sem contexto tem as seguintes desvantagens:

  • Outros assemblies não podem ser associados a assemblies que são carregados sem contexto, a menos que você manipule o evento AppDomain.AssemblyResolve.

  • As dependências não são carregadas automaticamente. Você pode pré-carregá-los sem contexto, pré-carregá-los no contexto de carga padrão ou carregá-los manipulando o AppDomain.AssemblyResolve evento.

  • Carregar vários assemblies com a mesma identidade sem contexto pode causar problemas de identidade de tipo semelhantes aos causados por carregar assemblies com a mesma identidade em vários contextos. Consulte Evite o carregamento de um assembly em vários contextos.

  • Se existir uma imagem nativa para o assembly, ela não será usada.

  • O assembly não pode ser carregado como de domínio neutro.

  • Nas versões do .NET Framework 1.0 e 1.1, a política não é aplicada.

Evite associações em nomes de assembly parciais

A associação de nome parcial ocorre quando você especifica apenas parte do nome de exibição do assembly (FullName) ao carregar um assembly. Por exemplo, você pode chamar o método Assembly.Load com somente o nome simples do assembly, omitindo a versão, a cultura e o token de chave pública. Ou você pode chamar o método Assembly.LoadWithPartialName, que primeiro chama o método Assembly.Load e, se esse não conseguir localizar o assembly, pesquisa o cache de assembly global e carrega a versão mais recente disponível do assembly.

A associação de nome parcial pode causar vários problemas, incluindo o seguinte:

  • O método Assembly.LoadWithPartialName pode carregar um assembly diferente com o mesmo nome simples. Por exemplo, dois aplicativos podem instalar dois assemblies completamente diferentes que têm o nome simples GraphicsLibrary no cache de assembly global.

  • O assembly realmente carregado pode não ser compatível com versões anteriores. Por exemplo, não especificar a versão pode resultar no carregamento de uma versão muito mais recente do que a versão que seu programa foi originalmente gravado para usar. Alterações na versão posterior podem causar erros em seu aplicativo.

  • O assembly realmente carregado pode não ser compatível com versões futuras. Por exemplo, você pode ter criado e testado seu aplicativo com a versão mais recente de um assembly, mas a associação parcial pode carregar uma versão muito anterior que não tem recursos usados pelo aplicativo.

  • A instalação de novos aplicativos pode interromper aplicativos existentes. Um aplicativo que usa o LoadWithPartialName método pode ser interrompido instalando uma versão mais recente e incompatível de um assembly compartilhado.

  • O carregamento de dependência inesperado pode ocorrer. Se você carregar dois assemblies que compartilham uma dependência, carregá-los com a associação parcial pode resultar em um assembly usando um componente com o qual ele não foi compilado ou testado.

Devido aos problemas que pode causar, o LoadWithPartialName método foi marcado como obsoleto. É recomendável que você use o método Assembly.Load em vez disso e especifique os nomes de exibição completos do assembly. Confira Entender as vantagens e desvantagens dos contextos de carga e considere alternar para o contexto de carga padrão.

Se você quiser usar o método LoadWithPartialName porque facilita o carregamento do assembly, considere que é mais provável que seu aplicativo forneça uma melhor experiência ao usuário se falhar com uma mensagem de erro que identifica o assembly ausente, do que usar automaticamente uma versão desconhecida do assembly, o que pode causar comportamento imprevisível e falhas de segurança.

Evite o carregamento de um assembly em vários contextos

Carregar um assembly em vários contextos pode causar problemas de identidade de tipo. Se o mesmo tipo for carregado do mesmo assembly em dois contextos diferentes, será como se dois tipos diferentes com o mesmo nome tivessem sido carregados. Um InvalidCastException é gerado se você tentar converter um tipo para o outro, com a mensagem confusa de que o tipo MyType não pode ser convertido para o tipo MyType.

Por exemplo, suponha que a interface ICommunicate é declarada em um assembly chamado Utility, que é referenciado por seu programa e também por outros assemblies que seu programa carrega. Esses outros assemblies contêm tipos que implementam a ICommunicate interface, permitindo que seu programa os use.

Agora considere o que acontece quando seu programa é executado. Os assemblies referenciados pelo seu programa são carregados no contexto padrão de carregamento. Se você carregar um assembly de destino por sua identidade, usando o método Load, ele estará no contexto de carregamento padrão, assim como suas dependências. Tanto o seu programa quanto a montagem de destino usarão a mesma Utility montagem.

No entanto, suponha que você carregue o assembly de destino por seu caminho de arquivo, usando o método LoadFile. O assembly é carregado sem nenhum contexto, portanto, suas dependências não são carregadas automaticamente. Você pode ter um manipulador para fornecer a dependência ao evento AppDomain.AssemblyResolve, e ele pode carregar o assembly Utility sem contexto usando o método LoadFile. Agora, quando você cria uma instância de um tipo contido no assembly de destino e tenta atribuí-la a uma variável de tipo ICommunicate, uma InvalidCastException é gerada porque o runtime considera as ICommunicate interfaces nas duas cópias do Utility assembly como tipos diferentes.

Há muitos outros cenários em que um assembly pode ser carregado em vários contextos. A melhor abordagem é evitar conflitos realocando o assembly de destino no caminho do aplicativo e usando o método Load com o nome de exibição completo. O assembly é então carregado no contexto de carregamento padrão e ambos os assemblies usam o mesmo assembly Utility.

Se o assembly de destino dever permanecer fora do caminho do aplicativo, você pode usar o método LoadFrom para carregá-lo no contexto de origem de carregamento. Se o assembly de destino foi compilado com uma referência ao assembly Utility do seu aplicativo, ele usará o assembly Utility em que seu aplicativo carregou no contexto de carregamento padrão. Observe que podem ocorrer problemas se o assembly de destino tiver uma dependência em uma cópia do assembly Utility localizado fora do caminho do aplicativo. Se o assembly for carregado no contexto de origem de carregamento antes de seu aplicativo carregar o assembly Utility, o carregamento do aplicativo falhará.

A seção Considere a possibilidade de alternar para o contexto de carregamento padrão discute alternativas ao uso de cargas de caminho de arquivo como LoadFile e LoadFrom.

Evite carregar várias versões de um assembly no mesmo contexto

Carregar várias versões de um assembly em um contexto de carregamento pode causar problemas de identidade de tipo. Se o mesmo tipo for carregado de duas versões do mesmo assembly, será como se dois tipos diferentes com o mesmo nome tivessem sido carregados. Um InvalidCastException é gerado se você tentar converter um tipo para o outro, com a mensagem confusa de que o tipo MyType não pode ser convertido para o tipo MyType.

Por exemplo, seu programa pode carregar uma versão do Utility assembly diretamente e, posteriormente, ele pode carregar outro assembly que carrega uma versão diferente do Utility assembly. Ou um erro de codificação pode fazer com que dois caminhos de código diferentes em seu aplicativo carreguem versões diferentes de um assembly.

No contexto de carregamento padrão, esse problema pode ocorrer quando você usa o método Assembly.Load e especifica os nomes de exibição completos do assembly que incluem números de versão diferentes. Para assemblies que são carregados sem contexto, o problema pode ser causado pelo uso do método Assembly.LoadFile para carregar o mesmo assembly de diferentes caminhos. O runtime considera dois conjuntos carregados de caminhos diferentes como distintos, mesmo que suas identidades sejam iguais.

Além dos problemas de identidade de tipo, várias versões de um assembly podem causar um MissingMethodException se um tipo carregado de uma versão do assembly é passado para o código que espera esse tipo de uma versão diferente. Por exemplo, o código pode esperar um método que foi adicionado à versão posterior.

Erros mais sutis podem ocorrer se o comportamento do tipo foi alterado entre as versões. Por exemplo, um método pode gerar uma exceção inesperada ou retornar um valor inesperado.

Examine cuidadosamente seu código para garantir que apenas uma versão de um assembly seja carregada. Você pode usar o método AppDomain.GetAssemblies para determinar quais assemblies são carregados em um determinado momento.

Considere trocar para o contexto de carga padrão

Examine os padrões de implantação e carregamento de assembly do aplicativo. Você pode eliminar os assemblies carregados de matrizes de bytes? Você pode mover assemblies para o caminho de investigação? Se os assemblies estão localizados no cache de assembly global ou caminho de investigação do domínio do aplicativo (isto é, seu ApplicationBase e PrivateBinPath), você pode carregar o assembly por sua identidade.

Se não for possível colocar todos os assemblies no caminho de investigação, considere alternativas como usar o modelo de suplemento do .NET Framework, colocar os assemblies no cache de assembly global ou criar domínios de aplicativo.

Considere usar o modelo de Add-In do .NET Framework

Se você estiver usando o contexto de carregamento para implementar suplementos, que normalmente não são instalados na base de aplicativo, use o modelo de suplemento do .NET Framework. Esse modelo fornece isolamento no domínio do aplicativo ou no nível do processo, sem exigir que você gerencie domínios de aplicativo por conta própria. Para obter informações sobre o modelo de suplemento, consulte Suplementos e Extensibilidade.

Considere usar o cache de assembly global

Coloque os assemblies no cache de assembly global para obter o benefício de um caminho de assembly compartilhado que está fora da base de aplicativo, sem perder as vantagens do contexto de carregamento padrão ou assumir as desvantagens dos outros contextos.

Considere usar domínios de aplicativo

Se você determinar que alguns de seus componentes não podem ser implantados no caminho de verificação do aplicativo, considere criar um novo domínio de aplicativo para esses componentes. Use um AppDomainSetup para criar o novo domínio do aplicativo e use a propriedade AppDomainSetup.ApplicationBase para especificar o caminho que contém os assemblies que você deseja carregar. Se você tiver vários diretórios para investigar, poderá definir como ApplicationBase um diretório raiz e usar a AppDomainSetup.PrivateBinPath propriedade para identificar os subdiretórios a serem investigados. Como alternativa, você pode criar vários domínios de aplicativo e definir o ApplicationBase domínio de cada aplicativo como o caminho apropriado para seus assemblies.

Observe que você pode usar o método Assembly.LoadFrom para carregar esses assemblies. Como agora eles estão no caminho de investigação, eles serão carregados no contexto de carregamento padrão em vez de no contexto de origem do carregamento. No entanto, recomendamos que você alterne para o método Assembly.Load e forneça nomes de exibição completos do assembly para garantir que as versões corretas sejam sempre usadas.

Consulte também