C# Bibliotecas de Classes

A Ana escreveu as regras do boletim da escola: lançar notas, calcular a média, dizer se o aluno passou. Agora ela quer usar essas mesmas regras em dois programas: o que a professora usa para lançar as notas e o que imprime o relatório da turma. Copiar o código para os dois seria pedir problema: uma correção num, esquecida no outro. A saída é pôr as regras numa biblioteca de classes: um projeto que não roda sozinho, só serve para outros projetos usarem.

Você vai aprender
  • A criar uma biblioteca com dotnet new classlib e o que muda no .csproj.
  • O que outro projeto enxerga: public × internal e o erro CS0122.
  • A ligar a biblioteca a um programa (dotnet reference add) e onde o .dll dela vai parar.
  • A usar a mesma biblioteca em dois programas.
  • Como uma exceção da biblioteca aparece no programa.
  • A documentar a biblioteca com /// (e o aviso CS1591) e a empacotá-la com dotnet pack.

Todas as saídas desta aula são reais, feitas com o .NET 10 SDK (10.0.112) na máquina fictícia da Ana, na solução ~/projetos/Escola. Antes, leia Soluções e Vários Projetos, que mostra a solução e o dotnet reference add.


A ideia: a caixa de ferramentas

Um eletricista não fabrica uma chave de fenda para cada serviço. Ele tem uma caixa de ferramentas e a leva para todas as casas. A caixa não faz nada sozinha: quem trabalha é o eletricista, usando as ferramentas dela.

Na oficinaNo .NET
A caixa de ferramentasA biblioteca de classes (Escola.Notas): classes prontas, sem Main
Cada serviço numa casaCada programa (Escola.App, Escola.Relatorio): tem por onde começar a rodar
As ferramentas que ficam à mostraAs classes public
As peças de dentro da caixa (que o cliente não mexe)As classes internal
Uma caixa vendida na lojaUm pacote NuGet (dotnet pack)

Você já usa bibliotecas o tempo todo: Console, List<T> e File moram em bibliotecas do .NET, e o Humanizer da aula Pacotes NuGet é uma biblioteca de outra pessoa. Agora você vai escrever a sua.


1. Criar a biblioteca: dotnet new classlib

$ mkdir Escola && cd Escola
$ dotnet new sln > /dev/null
$ dotnet new classlib -o src/Escola.Notas > /dev/null
$ ls src/Escola.Notas
Class1.cs
Escola.Notas.csproj
obj
$ cat src/Escola.Notas/Escola.Notas.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>
$ cat src/Escola.Notas/Class1.cs
namespace Escola.Notas;

public class Class1
{

}
dotnet new classlib
O modelo "Biblioteca de Classes" (class library). O > /dev/null só esconde as mensagens de sempre do dotnet new.
sem <OutputType>
Compare com o .csproj de um console (veja O Arquivo .csproj): falta a linha <OutputType>Exe</OutputType>. Sem ela, o padrão é Library: o resultado é só um .dll, sem executável.
Class1.cs
Uma classe vazia de exemplo, no lugar do Program.cs. Não há instruções de nível superior: uma biblioteca só tem classes (e outros tipos).
namespace Escola.Notas;
O namespace vem do nome do projeto. É por ele que os outros projetos vão chamar as classes (using Escola.Notas;, veja Namespaces e using).

Uma biblioteca não roda:

$ dotnet run
Não é possível continuar com o projeto "/home/ana/projetos/Escola/src/Escola.Notas/Escola.Notas.csproj".
Verifique se você tem um tipo de projeto executável.
Um projeto executável deve ter como destino um TFM executável (por exemplo, net10.0) e ter OutputType "Exe".
O OutputType atual é "Library".

A mensagem é clara: para rodar, o projeto precisaria de OutputType Exe (o "TFM" é o Target Framework Moniker, o net10.0 do TargetFramework). Mas a biblioteca não é para rodar: é para ser usada.


2. As classes da biblioteca: public e internal

A Ana apagou o Class1.cs e escreveu dois arquivos. O primeiro é a classe que os programas vão usar:

namespace Escola.Notas;

public class Boletim(string aluno)
{
    private readonly List<decimal> _notas = [];

    public string Aluno { get; } = aluno;

    public void Lancar(decimal nota)
    {
        if (nota < 0 || nota > 10)
        {
            throw new ArgumentOutOfRangeException(nameof(nota), "A nota vai de 0 a 10.");
        }
        _notas.Add(nota);
    }

    public decimal Media => _notas.Count == 0 ? 0 : Regras.Arredondar(_notas.Average());

    public string Situacao => Media >= Regras.MediaMinima ? "Aprovado" : "Recuperação";
}

O segundo guarda as regras da escola, que só o Boletim usa:

namespace Escola.Notas;

internal static class Regras
{
    public const decimal MediaMinima = 7.0m;

    public static decimal Arredondar(decimal valor) => Math.Round(valor, 1);
}
public class Boletim
public ("público"): a classe fica visível para os outros projetos que usarem a biblioteca. É a "ferramenta à mostra".
internal static class Regras
internal ("interno"): visível só dentro deste projeto. O Boletim usa as Regras, mas os programas não enxergam essa classe. Assim, a Ana pode mudar as regras (o arredondamento, a média mínima) sem quebrar nenhum programa.
sem modificador
Uma classe sem public nem internal é internal. Por isso, numa biblioteca, esquecer o public deixa a classe invisível para os programas (veja Modificadores de Acesso).
throw new ArgumentOutOfRangeException(…)
A biblioteca não sabe se está num console, num site ou num celular, então ela não escreve mensagens na tela: ela lança uma exceção e deixa o programa decidir o que mostrar (veja throw).
ModificadorQuem enxergaUse para
publicTodo mundo: este projeto e quem o referenciarO que os programas devem usar (o Boletim)
internal (o padrão das classes)Só o próprio projetoOs detalhes de dentro (as Regras)
private (o padrão dos membros)Só a própria classeOs campos (_notas)

3. Um programa que usa a biblioteca

$ dotnet new console -o src/Escola.App > /dev/null
$ dotnet sln add src/Escola.Notas src/Escola.App
O projeto ‘src/Escola.Notas/Escola.Notas.csproj’ foi adicionado à solução.
O projeto ‘src/Escola.App/Escola.App.csproj’ foi adicionado à solução.
$ dotnet reference add src/Escola.Notas --project src/Escola.App
A referência ‘..\Escola.Notas\Escola.Notas.csproj’ foi adicionada ao projeto.
using Escola.Notas;

var ana = new Boletim("Ana");
ana.Lancar(8.5m);
ana.Lancar(6.0m);
ana.Lancar(9.25m);

Console.WriteLine($"{ana.Aluno}: média {ana.Media} ({ana.Situacao})");
$ dotnet build
  Determinando os projetos a serem restaurados...
  /home/ana/projetos/Escola/src/Escola.App/Escola.App.csproj restaurado (em 229 ms).
  1 de 2 projetos estão atualizados para restauração.
  Escola.Notas -> /home/ana/projetos/Escola/src/Escola.Notas/bin/Debug/net10.0/Escola.Notas.dll
  Escola.App -> /home/ana/projetos/Escola/src/Escola.App/bin/Debug/net10.0/Escola.App.dll

Compilação com êxito.
    0 Aviso(s)
    0 Erro(s)

Tempo Decorrido 00:00:10.19
$ dotnet run --project src/Escola.App
Ana: média 7,9 (Aprovado)

(8,5 + 6,0 + 9,25) ÷ 3 = 7,9166…, arredondado para uma casa: 7,9. O programa só conhece o Boletim; a conta e o arredondamento ficaram dentro da biblioteca.

Para onde vai o .dll da biblioteca

$ ls src/Escola.Notas/bin/Debug/net10.0/
Escola.Notas.deps.json
Escola.Notas.dll
Escola.Notas.pdb
$ ls src/Escola.App/bin/Debug/net10.0/
Escola.App
Escola.App.deps.json
Escola.App.dll
Escola.App.pdb
Escola.App.runtimeconfig.json
Escola.Notas.dll
Escola.Notas.pdb
Escola.Notas.dll (na pasta da biblioteca)
A biblioteca compilada. Não há executável nem runtimeconfig.json: ela não roda sozinha.
Escola.Notas.dll (na pasta do programa)
O dotnet build copiou a biblioteca para junto do programa. Quando o Escola.App roda, ele procura o Escola.Notas.dll na própria pasta. Para levar o programa para outro computador, as duas .dll têm de ir juntas (a aula Publicar o Programa faz isso por você).
.pdb
As informações de depuração: os nomes dos arquivos e os números das linhas (seção 5 e Depuração).

No editor do guia não há projetos separados: os arquivos das abas são compilados juntos, como um projeto só. Mesmo assim dá para treinar a divisão em arquivos:

Missão: o Program.cs quer mostrar a maior nota da Ana, mas a classe Boletim ainda não tem essa propriedade: rode e veja o erro CS1061. Na aba Boletim.cs, acrescente public decimal Maior => _notas.Max(); e rode de novo. (No editor, a aba Regras.cs também seria visível para o Program.cs, porque tudo é um projeto só; num projeto separado, o internal a esconderia.)

using Escola.Notas;

var ana = new Boletim("Ana");
ana.Lancar(8.5m);
ana.Lancar(6.0m);
ana.Lancar(9.25m);

Console.WriteLine($"{ana.Aluno}: média {ana.Media} ({ana.Situacao})");
Console.WriteLine($"Maior nota: {ana.Maior}");
namespace Escola.Notas;

public class Boletim(string aluno)
{
    private readonly List<decimal> _notas = [];

    public string Aluno { get; } = aluno;

    public void Lancar(decimal nota)
    {
        if (nota < 0 || nota > 10)
        {
            throw new ArgumentOutOfRangeException(nameof(nota), "A nota vai de 0 a 10.");
        }
        _notas.Add(nota);
    }

    public decimal Media => _notas.Count == 0 ? 0 : Regras.Arredondar(_notas.Average());

    public string Situacao => Media >= Regras.MediaMinima ? "Aprovado" : "Recuperação";
}
namespace Escola.Notas;

internal static class Regras
{
    public const decimal MediaMinima = 7.0m;

    public static decimal Arredondar(decimal valor) => Math.Round(valor, 1);
}
namespace Escola.Notas;

public class Boletim(string aluno)
{
    private readonly List<decimal> _notas = [];

    public string Aluno { get; } = aluno;

    public void Lancar(decimal nota)
    {
        if (nota < 0 || nota > 10)
        {
            throw new ArgumentOutOfRangeException(nameof(nota), "A nota vai de 0 a 10.");
        }
        _notas.Add(nota);
    }

    public decimal Media => _notas.Count == 0 ? 0 : Regras.Arredondar(_notas.Average());

    public string Situacao => Media >= Regras.MediaMinima ? "Aprovado" : "Recuperação";

    public decimal Maior => _notas.Max();
}

4. A mesma biblioteca em dois programas

Agora o motivo de tudo isso: um segundo programa, o relatório da turma, usando as mesmas regras.

$ dotnet new console -o src/Escola.Relatorio > /dev/null
$ dotnet sln add src/Escola.Relatorio
O projeto ‘src/Escola.Relatorio/Escola.Relatorio.csproj’ foi adicionado à solução.
$ dotnet reference add src/Escola.Notas --project src/Escola.Relatorio
A referência ‘..\Escola.Notas\Escola.Notas.csproj’ foi adicionada ao projeto.
using Escola.Notas;

var turma = new List<Boletim>();

var ana = new Boletim("Ana");
ana.Lancar(8.5m); ana.Lancar(6.0m); ana.Lancar(9.25m);
turma.Add(ana);

var beto = new Boletim("Beto");
beto.Lancar(5.0m); beto.Lancar(7.5m);
turma.Add(beto);

foreach (Boletim b in turma.OrderByDescending(b => b.Media))
{
    Console.WriteLine($"{b.Aluno,-6} {b.Media,4} {b.Situacao}");
}
$ dotnet build -v q 2>&1 | tail -4
    0 Aviso(s)
    0 Erro(s)

Tempo Decorrido 00:00:02.87
$ dotnet run --project src/Escola.Relatorio
Ana     7,9 Aprovado
Beto    6,2 Recuperação

A solução agora tem três projetos: uma biblioteca e dois programas que a usam. Se a escola mudar a média mínima para 6,0, a Ana muda uma linha em Regras.cs, e os dois programas passam a seguir a regra nova no próximo dotnet build.

Repare na média do Beto. (5,0 + 7,5) ÷ 2 = 6,25, e o Math.Round(6.25m, 1) deu 6,2, não 6,3. Quando o número está exatamente no meio, o Math.Round arredonda para o vizinho par (o "arredondamento bancário"). Se a escola quiser o arredondamento da escola (6,25 → 6,3), a regra vira Math.Round(valor, 1, MidpointRounding.AwayFromZero), e a mudança é numa linha só da biblioteca (veja Math).

5. Uma exceção que atravessa projetos

A Ana lançou uma nota 11, por engano, no Escola.App:

$ echo 'ana.Lancar(11m);' >> src/Escola.App/Program.cs
$ dotnet run --project src/Escola.App; echo "código de saída: $?"
Ana: média 7,9 (Aprovado)
Unhandled exception. System.ArgumentOutOfRangeException: A nota vai de 0 a 10. (Parameter 'nota')
   at Escola.Notas.Boletim.Lancar(Decimal nota) in /home/ana/projetos/Escola/src/Escola.Notas/Boletim.cs:line 13
   at Program.<Main>$(String[] args) in /home/ana/projetos/Escola/src/Escola.App/Program.cs:line 9
código de saída: 134
A nota vai de 0 a 10. (Parameter 'nota')
A mensagem que a biblioteca escreveu, mais o nome do parâmetro, vindo do nameof(nota).
at Escola.Notas.Boletim.Lancar … Boletim.cs:line 13
Onde a exceção nasceu: dentro da biblioteca, na linha 13 do Boletim.cs (o throw). O nome completo inclui o namespace: Escola.Notas.Boletim.
at Program.<Main>$ … Program.cs:line 9
Quem chamou: a linha 9 do programa, o ana.Lancar(11m). A pilha atravessa os dois projetos (veja Exceções). Os números de linha vêm dos arquivos .pdb.

O erro é do programa (passou uma nota inválida), e quem tem de tratar é ele, com try/catch (veja try e catch). A biblioteca fez a parte dela: recusou a nota e explicou por quê.


6. Documentar a biblioteca: ///

Quem usa uma biblioteca não lê o código dela: lê a documentação. No C#, ela é escrita em comentários com três barras, ///, logo acima de cada coisa public. Para o compilador cobrar esses comentários, a Ana ligou no Escola.Notas.csproj:

    <GenerateDocumentationFile>true</GenerateDocumentationFile>
$ dotnet build src/Escola.Notas 2>&1 | grep warning | sort -u
/home/ana/projetos/Escola/src/Escola.Notas/Boletim.cs(18,20): warning CS1591: Comentário XML ausente para tipo publicamente visível ou membro "Boletim.Media" [/home/ana/projetos/Escola/src/Escola.Notas/Escola.Notas.csproj]
/home/ana/projetos/Escola/src/Escola.Notas/Boletim.cs(20,19): warning CS1591: Comentário XML ausente para tipo publicamente visível ou membro "Boletim.Situacao" [/home/ana/projetos/Escola/src/Escola.Notas/Escola.Notas.csproj]
/home/ana/projetos/Escola/src/Escola.Notas/Boletim.cs(3,14): warning CS1591: Comentário XML ausente para tipo publicamente visível ou membro "Boletim.Boletim(string)" [/home/ana/projetos/Escola/src/Escola.Notas/Escola.Notas.csproj]
/home/ana/projetos/Escola/src/Escola.Notas/Boletim.cs(3,14): warning CS1591: Comentário XML ausente para tipo publicamente visível ou membro "Boletim" [/home/ana/projetos/Escola/src/Escola.Notas/Escola.Notas.csproj]
/home/ana/projetos/Escola/src/Escola.Notas/Boletim.cs(7,19): warning CS1591: Comentário XML ausente para tipo publicamente visível ou membro "Boletim.Aluno" [/home/ana/projetos/Escola/src/Escola.Notas/Escola.Notas.csproj]
/home/ana/projetos/Escola/src/Escola.Notas/Boletim.cs(9,17): warning CS1591: Comentário XML ausente para tipo publicamente visível ou membro "Boletim.Lancar(decimal)" [/home/ana/projetos/Escola/src/Escola.Notas/Escola.Notas.csproj]

Seis avisos CS1591 ("comentário XML ausente para tipo ou membro visível publicamente"): um para cada coisa public. As Regras, que são internal, não precisam. A Ana documentou o Boletim:

namespace Escola.Notas;

/// <summary>As notas de um aluno e a situação dele.</summary>
/// <param name="aluno">O nome do aluno.</param>
public class Boletim(string aluno)
{
    private readonly List<decimal> _notas = [];

    /// <summary>O nome do aluno.</summary>
    public string Aluno { get; } = aluno;

    /// <summary>Lança uma nota de 0 a 10.</summary>
    /// <exception cref="ArgumentOutOfRangeException">A nota é menor que 0 ou maior que 10.</exception>
    public void Lancar(decimal nota)
    {
        «… igual …»
    }

    /// <summary>A média das notas, com uma casa decimal (0 se não houver notas).</summary>
    public decimal Media => _notas.Count == 0 ? 0 : Regras.Arredondar(_notas.Average());

    /// <summary>"Aprovado" com média 7,0 ou mais; senão, "Recuperação".</summary>
    public string Situacao => Media >= Regras.MediaMinima ? "Aprovado" : "Recuperação";
}
$ dotnet build src/Escola.Notas 2>&1 | grep -E "warning|Aviso"
    0 Aviso(s)
$ ls src/Escola.Notas/bin/Debug/net10.0/
Escola.Notas.deps.json
Escola.Notas.dll
Escola.Notas.pdb
Escola.Notas.xml
$ head -12 src/Escola.Notas/bin/Debug/net10.0/Escola.Notas.xml
<?xml version="1.0"?>
<doc>
    <assembly>
        <name>Escola.Notas</name>
    </assembly>
    <members>
        <member name="T:Escola.Notas.Boletim">
            <summary>As notas de um aluno e a situação dele.</summary>
            <param name="aluno">O nome do aluno.</param>
        </member>
        <member name="M:Escola.Notas.Boletim.#ctor(System.String)">
            <summary>As notas de um aluno e a situação dele.</summary>
<summary>
"Resumo": uma frase dizendo o que a coisa é ou faz.
<param name="aluno">
O que é cada parâmetro.
<exception cref="…">
Que exceção pode sair dali, e quando.
Escola.Notas.xml
O compilador juntou os comentários num arquivo ao lado do .dll. Os editores (VS Code, Visual Studio, Rider) leem esse arquivo e mostram o resumo quando você passa o mouse sobre Lancar no programa. É assim que aparece a explicação de Console.WriteLine no seu editor.

Os comentários de documentação têm aula própria: veja Comentários.


7. Empacotar: dotnet pack

Para outras pessoas (ou outras soluções) usarem a biblioteca como um pacote NuGet:

$ dotnet pack src/Escola.Notas -c Release
  Determinando os projetos a serem restaurados...
  Todos os projetos estão atualizados para restauração.
  Escola.Notas -> /home/ana/projetos/Escola/src/Escola.Notas/bin/Release/net10.0/Escola.Notas.dll
  Falta um pacote Escola.Notas.1.0.0 Acesse. Acesse https://aka.ms/nuget/authoring-best-practices/readme para saber porque readmes de pacote são importantes.
  Pacote criado com êxito '/home/ana/projetos/Escola/src/Escola.Notas/bin/Release/Escola.Notas.1.0.0.nupkg'.
$ ls src/Escola.Notas/bin/Release/
Escola.Notas.1.0.0.nupkg
net10.0
dotnet pack … -c Release
"Empacote": compila em Release e cria um .nupkg, o mesmo tipo de arquivo que vem do nuget.org.
Escola.Notas.1.0.0.nupkg
Nome e versão. Sem <Version> no .csproj, a versão é 1.0.0.
Falta um pacote … Acesse.
A tradução saiu truncada, mas o original diz: "o pacote Escola.Notas.1.0.0 não tem um README". É uma recomendação, não um erro: o nuget.org mostra o README na página do pacote.

Esse .nupkg pode ser publicado no nuget.org (com uma conta e uma chave) ou num servidor da empresa. Para uma solução só, como a da Ana, a referência de projeto é mais simples: não precisa empacotar nada.


Biblioteca ou só uma pasta no projeto?

SituaçãoEscolha
Um programa só, código organizado em vários arquivosPastas e namespaces dentro do mesmo projeto (veja Namespaces).
As mesmas regras em dois ou mais programas (console, site, testes)Biblioteca de classes + dotnet reference add.
Separar as regras da tela (o boletim não sabe que existe um console)Biblioteca: fica claro o que é regra e o que é tela.
Testar as regras (veja Testes)Biblioteca: o projeto de testes a referencia como qualquer programa.
Outras pessoas, em outras soluções, vão usarBiblioteca + dotnet pack (pacote NuGet).

Quando dá errado

error CS0122: "Regras" é inacessível devido ao seu nível de proteção
$ echo 'Console.WriteLine(Regras.MediaMinima);' >> src/Escola.App/Program.cs
$ dotnet build 2>&1 | grep error | sort -u
/home/ana/projetos/Escola/src/Escola.App/Program.cs(9,19): error CS0122: "Regras" é inacessível devido ao seu nível de proteção [/home/ana/projetos/Escola/src/Escola.App/Escola.App.csproj]

O que aconteceu: o programa tentou usar a classe Regras, que é internal na biblioteca. O compilador sabe que ela existe, mas não deixa usar (em inglês, 'Regras' is inaccessible due to its protection level). Se a classe tivesse ficado sem modificador, o erro seria o mesmo, porque o padrão é internal.

Como resolver: se o programa precisa mesmo daquilo, torne public. Se não (como aqui), deixe internal e use o que a biblioteca oferece em público (Boletim.Situacao). Para um projeto de testes enxergar o internal, existe o atributo [assembly: InternalsVisibleTo("Escola.Notas.Tests")].

Não é possível continuar com o projeto … O OutputType atual é "Library".

O que aconteceu: o dotnet run foi chamado na pasta da biblioteca (seção 1).

Como resolver: rode o programa que usa a biblioteca: dotnet run --project src/Escola.App.

error CS0246: O nome do tipo ou do namespace "Escola" não pode ser encontrado

O que aconteceu: o programa não tem a referência para a biblioteca, ou falta o using Escola.Notas; (veja o mesmo erro em Soluções).

Como resolver: dotnet reference list --project src/Escola.App mostra as referências. Se a biblioteca não estiver lá: dotnet reference add src/Escola.Notas --project src/Escola.App.


Referência rápida

Comando ou códigoO que faz
dotnet new classlib -o src/NomeCria uma biblioteca de classes.
dotnet reference add src/Biblioteca --project src/ProgramaO programa passa a usar a biblioteca.
public class …Visível para quem usa a biblioteca.
internal class … (ou sem modificador)Só dentro da biblioteca.
<GenerateDocumentationFile>true</GenerateDocumentationFile>Gera o .xml da documentação e avisa (CS1591) o que falta documentar.
/// <summary>…</summary>Comentário de documentação.
dotnet pack -c ReleaseCria o pacote .nupkg.
[assembly: InternalsVisibleTo("Outro")]Deixa o projeto Outro (os testes) ver o internal.

Exercícios

1. Criar a biblioteca

Complete o comando que cria uma biblioteca de classes em src/Loja.Precos:

dotnet new -o src/Loja.Precos

2. Visível para os programas

Complete para que a classe possa ser usada por outros projetos:

class Tabela

3. Ligar o programa à biblioteca

Complete para o src/Loja.App usar a src/Loja.Precos:

dotnet reference add src/Loja.Precos

4. A Ana esqueceu o modificador: escreveu só class Boletim na biblioteca. O que acontece no programa que a usa?

5. Por que a biblioteca lança uma exceção em vez de escrever "nota inválida" com Console.WriteLine?

6. Depois do dotnet build, onde está o Escola.Notas.dll que o Escola.App usa ao rodar?


Resumo

  • Uma biblioteca de classes (dotnet new classlib) é um projeto sem OutputType Exe: vira um .dll que não roda sozinho, só é usado.
  • public é o que os outros projetos enxergam; internal (o padrão das classes) fica dentro da biblioteca.
  • O programa usa a biblioteca com dotnet reference add e using. O build copia o .dll da biblioteca para a pasta do programa.
  • Uma biblioteca serve a vários programas: uma correção nela vale para todos.
  • A biblioteca lança exceções; o programa decide o que mostrar.
  • /// documenta; GenerateDocumentationFile gera o .xml e cobra o que falta (CS1591). dotnet pack cria um pacote NuGet.