Práticas recomendadas para carregamento de montagem

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, compreender as vantagens e desvantagens dos contextos de carga, fornece informações de fundo para as outras recomendações, porque todas elas dependem de um conhecimento dos contextos de carga.

Compreender as vantagens e desvantagens dos contextos de carga

Dentro de 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 encontrados examinando o cache de assembly global, o armazenamento de assembly do host se o tempo de execução estiver hospedado (por exemplo, no SQL Server) e o ApplicationBase e PrivateBinPath do domínio do aplicativo. A maioria das sobrecargas do Load método carrega assemblies nesse contexto.

  • O contexto load-from contém assemblies que são carregados de locais que não são pesquisados pelo carregador. Por exemplo, os suplementos podem ser instalados em um diretório que não esteja sob o caminho do aplicativo. Assembly.LoadFrom, AppDomain.CreateInstanceFrome AppDomain.ExecuteAssembly são exemplos de métodos que carregam por caminho.

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

  • Se você gerou um assembly dinâmico transitório usando a emissão de reflexão, o assembly não está em nenhum contexto. Além disso, a maioria dos assemblies que são carregados usando o método são carregados sem contexto, e assemblies que são carregados de matrizes de bytes são carregados sem contexto, a menos que sua identidade (após a LoadFile aplicação da política) estabeleça que eles 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 carregamento padrão

Quando os assemblies são carregados no contexto de carga padrão, suas dependências são carregadas automaticamente. As dependências que são carregadas no contexto de carga padrão são encontradas automaticamente para assemblies no contexto de carga padrão ou no contexto load-from. O carregamento por identidade de assembly aumenta a estabilidade dos aplicativos, garantindo que versões desconhecidas de assemblies não sejam usadas (consulte a seção Evitar vinculação em nomes de assembly parcial).

Usar o contexto de carga padrão tem as seguintes desvantagens:

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

  • Não é possível carregar assemblies de locais fora do caminho de sondagem no contexto de carga padrão.

Contexto de carregamento a partir

O contexto load-from permite carregar um assembly de um caminho que não está sob o caminho do aplicativo e, portanto, não está incluído na sondagem. Ele permite que as dependências sejam localizadas e carregadas a partir desse caminho, porque as informações do caminho são mantidas pelo contexto. Além disso, assemblies nesse contexto podem usar dependências que são carregadas no contexto de carga padrão.

Carregar assemblies usando o Assembly.LoadFrom método, 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 load-from, LoadFrom retornará o assembly carregado mesmo que um caminho diferente tenha sido especificado.

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

  • Se um assembly for carregado com LoadFrom, e o caminho de sondagem incluir um assembly com a mesma identidade, mas em um local diferente, poderá ocorrer um InvalidCastException, MissingMethodExceptionou outro comportamento inesperado.

  • LoadFrom demandas 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 domínio neutro.

  • No .NET Framework versões 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 sondagem é 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 diretiva é aplicada, corresponda à identidade de um assembly no cache de assembly global; Nesse caso, o assembly é carregado a partir do cache de assembly global.

Carregar assemblies sem contexto tem as seguintes desvantagens:

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

  • 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 pelo carregamento de assemblies com a mesma identidade em vários contextos. Consulte Evitar carregar 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 domínio neutro.

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

Evite a vinculação em nomes de assembly parcial

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

A vinculação parcial de nomes pode causar muitos problemas, incluindo os seguintes:

  • O Assembly.LoadWithPartialName método 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 GraphicsLibrary simples no cache de assembly global.

  • O assembly que é 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 tardia do que a versão que o programa foi originalmente escrito para usar. Alterações na versão posterior podem causar erros em seu aplicativo.

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

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

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

Devido aos problemas que pode causar, o LoadWithPartialName método foi marcado como obsoleto. Recomendamos que você use o Assembly.Load método em vez disso e especifique nomes de exibição de assembly completos. Consulte Compreender as vantagens e desvantagens dos contextos de carga e considere mudar para o contexto de carga padrão.

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

Evite carregar um assembly em vários contextos

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

Por exemplo, suponha que a interface é declarada ICommunicate em um assembly chamado Utility, que é referenciado pelo seu programa e também por outros assemblies que o 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 o seu programa é executado. Os assemblies que são referenciados pelo seu programa são carregados no contexto de carga padrão. Se você carregar um assembly de destino por sua identidade, usando o Load método, ele estará no contexto de carregamento padrão, assim como suas dependências. Tanto o programa quanto o assembly de destino usarão o mesmo Utility assembly.

No entanto, suponha que você carregue o assembly de destino por seu caminho de arquivo, usando o LoadFile método. O assembly é carregado sem qualquer contexto, portanto, suas dependências não são carregadas automaticamente. Você pode ter um manipulador para o AppDomain.AssemblyResolve evento para fornecer a dependência e ele pode carregar o Utility assembly sem contexto usando o LoadFile método. Agora, quando você cria uma instância de um tipo que está contido no assembly de destino e tenta atribuí-la a uma variável do tipo ICommunicate, um InvalidCastException é lançado porque o tempo de execução 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 Load método com o nome de exibição completo. O assembly é então carregado no contexto de carga padrão e ambos os assemblies usam o mesmo Utility assembly.

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

A seção Considere alternar para o contexto de carga padrão discute alternativas ao uso de carregamentos 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 é carregado a partir de duas versões do mesmo assembly, é como se dois tipos diferentes com o mesmo nome tivessem sido carregados. Um InvalidCastException é lançado se você tentar converter um tipo para o outro, com a mensagem confusa de que o tipo MyType não pode ser convertido para digitar MyType.

Por exemplo, seu programa pode carregar uma versão do Utility assembly diretamente e, mais tarde, 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 Assembly.Load método e especificar nomes de exibição de assembly completo que incluem números de versão diferentes. Para assemblies que são carregados sem contexto, o problema pode ser causado usando o Assembly.LoadFile método para carregar o mesmo assembly de caminhos diferentes. O tempo de execução considera dois assemblies que são carregados de caminhos diferentes como assemblies diferentes, mesmo que suas identidades sejam as mesmas.

Além de problemas de identidade de tipo, várias versões de um assembly podem causar um MissingMethodException se um tipo que é 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 mudou entre as versões. Por exemplo, um método pode lançar uma exceção inesperada ou retornar um valor inesperado.

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

Considere mudar para o contexto de carregamento padrão

Examine os padrões de montagem e implantação do seu aplicativo. Você pode eliminar assemblies que são carregados de matrizes de bytes? É possível mover montagens para o caminho de sondagem? Se os assemblies estiverem localizados no cache de assembly global ou no caminho de sondagem do domínio do aplicativo (ou seja, seu ApplicationBase e PrivateBinPath), você poderá carregar o assembly por sua identidade.

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

Considere usar o modelo de suplemento do .NET Framework

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

Considere o uso do cache de assembly global

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

Considere o uso de domínios de aplicativo

Se você determinar que alguns de seus assemblies não podem ser implantados no caminho de sondagem do aplicativo, considere a criação de um novo domínio de aplicativo para esses assemblies. Use um AppDomainSetup para criar o novo domínio de aplicativo e use a AppDomainSetup.ApplicationBase propriedade para especificar o caminho que contém os assemblies que você deseja carregar. Se você tiver vários diretórios para sondar, poderá definir o ApplicationBase como 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 de cada domínio de aplicativo para o caminho apropriado para seus assemblies.

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

Consulte também