Esse é o 14º post da série sobre C# 7, e o 4º e último sobre C# 7.1. Pra acompanhar a série você pode seguir a tag C#7 no blog ou voltar no post que agrega a série.

Novidades do C# 7.1: Gerando assemblies de referência

O compilador do C# 7.1 permite gerar assemblies de referência. Estes assemblies são usados somente por ferramentas para reflection, e não podem ser executados por um runtime. Se um projeto referenciar um assembly de referência diretamente o runtime, ao tentar carregá-lo, lançará uma exceção e o executável não vai iniciar. Isso acontece porque o atributo ReferenceAssembly está presente nos metadados do assembly.

Para ler assemblies de referência você pode usar métodos específicos de reflection, como por exemplo o ReflectionOnlyLoad. Nos docs da Microsoft você encontra mais informação de como, e porque, fazer isso.

Um assembly de referência não tem implementação, contendo throw null em todos os corpos de métodos, propriedades, etc. E um assembly de referência remove qualquer informação da compilação que não comporia a superfície de API daquele assembly. Ou seja, tipos privados, campos privados, etc, são todos removidos.

Claramente esse não é o tipo de funcionalidade que a maioria dos desenvolvedores vai utilizar. Na minha opinião isso foi criado pra facilitar a criação dos assemblies do .NET Standard. Ainda que assemblies de referência não sejam um assunto novo, o .NET Standard evolui mais rápido do que o .NET Framework e os perfis de PCL, e acredito que eles foram na direção de facilitar a própria vida. Não conheço mais ninguém gerando assemblies de referência fora a Microsoft, ainda assim, o assunto é interessante.

Caso você fique curiosa(o) e queira ver os tais assemblies, dê uma olhada no diretório C:\Program Files (x86)\Reference Assemblies, lá estarão os assemblies de referência do .NET Core, .NET Framework, etc. Caso queira dar uma olhada nos do .NET Standard, baixe o pacote NETStandard.Library e extraia uma dll, por exemplo a que está em /build/netstandard2.0/ref/netstandard.dll e abra-a utilizando o ilspy. Lá você verá os lançamentos de null. Por exemplo, aqui está o começo do tipo string:

namespace System
{
    public sealed class String : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable, ICloneable, IComparable, IComparable, IConvertible, IEquatable
    {
        public static readonly string Empty;

        public char this[int index] { get { throw null; } }

        public int Length { get { throw null; } }

        [CLSCompliant(false)]
        public unsafe String(char* value) { }
        // segue...

Não encontrei uma forma de gerar um reference assembly utilizando o Visual Studio, procurei nas propriedades do projeto de C#, e também procurei nos docs um elemento xml que pudéssemos colocar no arquivo de projeto (csproj). Se você descobrir conte aqui nos comentários como fazer. Por enquanto, estou entendendo que teremos que chamar o csc diretamente. Nem mesmo uma forma de chamar com MSBuild eu encontrei, a task Csc que vem nos targets do .NET Framework e do .NET Core não possuem uma forma de passar as opções necessárias.

Os suporte a criação de reference assemblies vem com as opções -refonly e -refout do compilador da versão 7.1. A única diferença entre elas é que a refout permite especificar o caminho onde os assemblies serão gerados. Para todos os outros efeitos, o resultado é o mesmo para ambas.

Para nós, que não trabalhamos na Microsoft, faz pouca diferença, mas que é interessante, isso é!

Giovanni Bassi

Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.