Assemblies de referência
Os assemblies de referência são um tipo especial de assembly que contém apenas a quantidade mínima de metadados necessários para representar a superfície de API pública da biblioteca. Eles incluem declarações para todos os membros que são significativas ao referenciar um assembly em ferramentas de build, mas excluem todas as implementações de membros e declarações de membros privados que não têm nenhum impacto observável em seu contrato de API. Por outro lado, assemblies regulares são chamados de assemblies de implementação.
Os assemblies de referência não podem ser carregados para execução, mas podem ser passados como entrada do compilador da mesma forma que os assemblies de implementação. Os assemblies de referência geralmente são distribuídos com o SDK (kit de desenvolvimento de software) de uma plataforma ou biblioteca específica.
O uso de um assembly de referência permite que os desenvolvedores criem programas direcionados a uma versão de biblioteca específica sem ter o assembly de implementação completo para essa versão. Suponha que você tenha apenas a versão mais recente de alguma biblioteca em seu computador, mas deseja criar um programa direcionado a uma versão anterior dessa biblioteca. Se você compilar diretamente no assembly de implementação, poderá usar inadvertidamente membros da API que não estão disponíveis na versão anterior. Você só encontrará esse erro ao testar o programa no computador de destino. Se você compilar no assembly de referência para a versão anterior, receberá imediatamente um erro de tempo de compilação.
Um assembly de referência também pode representar um contrato, ou seja, um conjunto de APIs que não correspondem ao assembly de implementação concreto. Esses assemblies de referência, chamados de assembly de contrato, podem ser usados para direcionar várias plataformas que dão suporte ao mesmo conjunto de APIs. Por exemplo, o .NET Standard fornece o assembly de contrato, netstandard.dll, que representa o conjunto de APIs comuns compartilhadas entre diferentes plataformas .NET. As implementações dessas APIs estão contidas em assemblies diferentes em diferentes plataformas, como mscorlib.dll no .NET Framework ou System.Private.CoreLib.dll no .NET Core. Uma biblioteca direcionada ao .NET Standard pode ser executada em todas as plataformas compatíveis com o .NET Standard.
Usando assemblies de referência
Para usar determinadas APIs do seu projeto, você deve adicionar referências aos assemblies deles. Você pode adicionar referências a assemblies de implementação ou a assemblies de referência. É recomendável que você use assemblies de referência sempre que eles estiverem disponíveis. Isso garante que você esteja usando apenas os membros da API com suporte na versão de destino, destinados a serem usados por designers de API. O uso do assembly de referência garante que você não esteja usando uma dependência dos detalhes da implementação.
Os assemblies de referência para as bibliotecas .NET Framework são distribuídos com pacotes de destino. Você pode obtê-los baixando um instalador autônomo ou selecionando um componente no instalador do Visual Studio. Para obter mais informações, confira Instalar o .NET Framework para desenvolvedores. Para .NET Core e .NET Standard, os assemblies de referência são baixados automaticamente conforme necessário (via NuGet) e referenciados. Para o .NET Core 3.0 e superior, os assemblies de referência para a estrutura principal estão no pacote Microsoft.NETCore.App.Ref (o pacote Microsoft.NETCore.App é usado em vez de versões antes da 3.0).
Quando você adiciona referências a assemblies do .NET Framework no Visual Studio usando a caixa de diálogo Adicionar referência, você seleciona um assembly na lista e o Visual Studio localiza automaticamente assemblies de referência que correspondem à versão da estrutura de destino selecionada em seu projeto. O mesmo se aplica à adição de referências diretamente ao projeto MSBuild usando o item de projeto de referência: você só precisa especificar o nome do assembly, não o caminho completo do arquivo. Ao adicionar referências a esses assemblies na linha de comando, usando a -reference
opção do compilador (em C# e no Visual Basic) ou usando o método Compilation.AddReferences na API Roslyn, você deve especificar manualmente os arquivos de assembly de referência para a versão correta da plataforma de destino. Os arquivos de assembly de referência do .NET Framework estão localizados no diretório %ProgramFiles(x86)%\Assemblies de referência\Microsoft\Framework\.NETFramework. Para o .NET Core, você pode forçar a operação de publicação para copiar assemblies de referência para sua plataforma de destino no subdiretório publish/refs do diretório de saída, definindo a propriedade do projeto PreserveCompilationContext
como true
. Em seguida, você pode passar esses arquivos de assembly de referência para o compilador. O uso de DependencyContext
do pacote Microsoft.Extensions.DependencyModel pode ajudar a localizar seus caminhos.
Como eles não contêm nenhuma implementação, os assemblies de referência não podem ser carregados para execução. Tentar fazer isso resulta em um System.BadImageFormatException. Se quiser examinar o conteúdo de um assembly de referência, você pode carregá-lo no contexto somente reflexão no .NET Framework (usando o método Assembly.ReflectionOnlyLoad) ou no MetadataLoadContext no .NET e no .NET Framework.
Gerando assemblies de referência
A geração de assemblies de referência para suas bibliotecas pode ser útil quando os consumidores da biblioteca precisarem criar seus programas em relação a muitas versões diferentes da biblioteca. Distribuir assemblies de implementação para todas essas versões pode ser impraticável devido ao tamanho grande. Os assemblies de referência são menores em tamanho e distribuí-los como parte do SDK da biblioteca reduz o tamanho do download e economiza espaço em disco.
IDEs e ferramentas de compilação também podem se beneficiar dos assemblies de referência para reduzir os tempos de compilação, em caso de grandes soluções que consistem em várias bibliotecas de classes. Normalmente, em cenários de compilação incrementais, um projeto é recriado quando qualquer um de seus arquivos de entrada é alterado, incluindo os assemblies dos quais ele depende. O assembly de implementação é alterado sempre que o programador altera a implementação de qualquer membro. O assembly de referência só muda quando sua API pública é afetada. Portanto, usar o assembly de referência como um arquivo de entrada em vez do assembly de implementação permite ignorar a compilação do projeto dependente em alguns casos.
Você pode gerar assemblies de referência:
- Em um projeto do MSBuild, usando a propriedade de projeto
ProduceReferenceAssembly
. - Ao compilar o programa da linha de comando, especificando as opções do compilador
-refonly
(C# / Visual Basic) ou-refout
(C# / Visual Basic). - Ao usar a API Roslyn, definindo EmitOptions.EmitMetadataOnly como
true
e EmitOptions.IncludePrivateMembers comofalse
em um objeto passado para o método Compilation.Emit.
Se você quiser distribuir assemblies de referência com pacotes NuGet, deverá incluí-los no subdiretório ref\ no diretório do pacote, em vez de no subdiretório lib\ usado para assemblies de implementação.
Estrutura de assemblies de referência
Os assemblies de referência são uma expansão do conceito relacionado, assemblies somente de metadados. Os assemblies somente de metadados têm seus corpos de método substituídos por um único corpo throw null
, mas incluem todos os membros, exceto tipos anônimos. O motivo para usar corpos throw null
(em vez de nenhum corpo) é que PEVerify poderia ser executado e passado (validando, assim, a integridade dos metadados).
Os assemblies de referência removem ainda mais metadados (membros particulares) de assemblies somente de metadados:
- Um assembly de referência tem somente referências para o que ele precisa na superfície de API. Talvez o assembly real tenha outras referências relacionadas a implementações específicas. Por exemplo, o assembly de referência para
class C { private void M() { dynamic d = 1; ... } }
não referencia nenhum tipo necessário paradynamic
. - Membros de função privados (métodos, propriedades e eventos) são removidos nos casos em que sua remoção não afeta nitidamente a compilação. Se não houver atributos InternalsVisibleTo, os membros da função interna também serão removidos.
Os metadados em assemblies de referência continuam a manter as seguintes informações:
- Todos os tipos, incluindo tipos privados e aninhados.
- Todos os atributos, até mesmo os internos.
- Todos os métodos virtuais.
- Implementações explícitas da interface.
- Propriedades e eventos explicitamente implementados, uma vez que seus acessadores são virtuais.
- Todos os campos de estruturas.
Os assemblies de referência incluem um atributo ReferenceAssembly de nível de assembly. Esse atributo pode ser especificado na origem, assim, o compilador não precisará sintetizá-lo. Por causa desse atributo, os runtimes se recusarão a carregar assemblies de referência para execução (mas podem ser carregados em modo somente reflexão).
Os detalhes exatos da estrutura do assembly de referência dependem da versão do compilador. Versões mais recentes podem optar por excluir mais metadados se forem determinadas como não afetando a superfície da API pública.
Observação
As informações nesta seção são aplicáveis somente a assemblies de referência gerados por compiladores Roslyn a partir da versão 7.1 do C# ou do Visual Basic versão 15.3. A estrutura de assemblies de referência para bibliotecas .NET Framework e .NET Core pode diferir em alguns detalhes, pois eles usam seu próprio mecanismo de geração de assemblies de referência. Por exemplo, eles podem ter corpos de método totalmente vazios, em vez do corpo throw null
. Mas o princípio geral ainda se aplica: eles não têm implementações de método utilizáveis e contêm metadados apenas para membros que têm um impacto observável de uma perspectiva de API pública.