Desenvolver bibliotecas com a CLI do .NET
Esse artigo aborda como escrever bibliotecas para .NET usando a CLI do .NET. A CLI fornece uma experiência eficiente e de baixo nível que funciona em qualquer sistema operacional com suporte. Você ainda pode criar bibliotecas com o Visual Studio e, se essa for sua experiência preferida, consultar o guia do Visual Studio.
Pré-requisitos
Você precisa do SDK do .NET instalado no seu computador.
Para as seções deste documento que tratam de versões do .NET Framework, é necessário ter o .NET Framework instalado em um computador Windows.
Além disso, se você quiser dar suporte a destinos mais antigos do .NET Framework, precisará instalar pacotes de direcionamento/desenvolvedor na página de arquivos de download do .NET Framework. Consulte esta tabela:
Versão do .NET Framework | O que baixar |
---|---|
4.6.1 | Pacote de Direcionamento do .NET Framework 4.6.1 |
4,6 | Pacote de Direcionamento do .NET Framework 4.6 |
4.5.2 | Pacote de Desenvolvedor do .NET Framework 4.5.2 |
4.5.1 | Pacote de Desenvolvedor do .NET Framework 4.5.1 |
4.5 | Software Development Kit do Windows (SDK do Windows) para Windows 8 |
4,0 | SDK do Windows para Windows 7 e .NET Framework 4 |
2.0, 3.0 e 3.5 | Runtime do .NET Framework 3.5 SP1 (ou versão do Windows 8 ou superior) |
Como direcionar o .NET 5+ ou o .NET Standard
Você controla a estrutura de destino do projeto adicionando-a ao arquivo de projeto (.csproj ou .fsproj). Para obter diretrizes sobre como escolher entre direcionar o .NET 5+ ou o .NET Standard, consulte .NET 5+ e .NET Standard.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>
Se desejar direcionar para o .NET Framework versões 4.0 ou inferior, ou se desejar usar uma API disponível no .NET Framework, mas não no .NET Standard (por exemplo, System.Drawing
), leia as seções a seguir para aprender a usar multiplataformas.
Como direcionar para o .NET Framework
Observação
Essas instruções pressupõem que você tenha o .NET Framework instalado no seu computador. Consulte os Pré-requisitos para obter as dependências instaladas.
Lembre que algumas das versões do .NET Framework usadas aqui não têm mais suporte. Consulte as Perguntas Frequentes de Política do Ciclo de Vida de Suporte do .NET Framework sobre versões sem suporte.
Se desejar alcançar o número máximo de projetos e desenvolvedores, use o .NET Framework 4.0 como seu destino de linha de base. Para direcionar para o .NET Framework, comece usando o TFM (Moniker da Estrutura de Destino) correto correspondente à versão do .NET Framework à qual você deseja dar suporte.
Versão do .NET Framework | TFM |
---|---|
.NET Framework 2.0 | net20 |
.NET Framework 3.0 | net30 |
.NET Framework 3.5 | net35 |
.NET Framework 4.0 | net40 |
.NET Framework 4.5 | net45 |
.NET Framework 4.5.1 | net451 |
.NET Framework 4.5.2 | net452 |
.NET Framework 4.6 | net46 |
.NET Framework 4.6.1 | net461 |
.NET Framework 4.6.2 | net462 |
.NET Framework 4.7 | net47 |
.NET Framework 4.8 | net48 |
Em seguida, insira esse TFM na seção TargetFramework
do arquivo de projeto. Por exemplo, aqui está como você escreveria uma biblioteca direcionada ao .NET Framework 4.0:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net40</TargetFramework>
</PropertyGroup>
</Project>
Pronto! Embora seja compilada somente para o .NET Framework 4, você pode usar a biblioteca em versões mais recentes do .NET Framework.
Como usar multiplataformas
Observação
As instruções a seguir pressupõem que você tenha o .NET Framework instalado no seu computador. Consulte a seção Pré-requisitos para saber quais dependências você precisa instalar e de onde baixá-las.
Talvez seja necessário direcionar para versões mais antigas do .NET Framework quando seu projeto der suporte ao .NET Framework e ao .NET. Nesse cenário, se você quiser usar construções de linguagem e APIs mais recentes para os destinos, use as diretivas #if
no seu código. Você também pode precisar adicionar diferentes pacotes e dependências para cada plataforma de destino para incluir as diferentes APIs necessárias para cada caso.
Por exemplo, digamos que você tem uma biblioteca que executa operações de rede por meio de HTTP. Para .NET Standard e .NET Framework versões 4.5 ou posterior, você pode usar a classe HttpClient
do namespace System.Net.Http
. No entanto, versões anteriores do .NET Framework não tem a classe HttpClient
, portanto, você pode usar a classe WebClient
do namespace System.Net
para elas.
O arquivo de projeto seria semelhante a:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net40;net45</TargetFrameworks>
</PropertyGroup>
<!-- Need to conditionally bring in references for the .NET Framework 4.0 target -->
<ItemGroup Condition="'$(TargetFramework)' == 'net40'">
<Reference Include="System.Net" />
</ItemGroup>
<!-- Need to conditionally bring in references for the .NET Framework 4.5 target -->
<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
<Reference Include="System.Net.Http" />
<Reference Include="System.Threading.Tasks" />
</ItemGroup>
</Project>
Você notará três alterações importantes aqui:
- O nó
TargetFramework
foi substituído peloTargetFrameworks
e três TFMs são expressas dentro. - Há um nó
<ItemGroup>
para o destinonet40
extraindo um que o .NET Framework referencia. - Há um nó
<ItemGroup>
para o destinonet45
extraindo dois que o .NET Framework referencia.
Símbolos do pré-processador.
O sistema de build reconhece os seguintes símbolos do pré-processador usados nas diretivas #if
:
Frameworks de destino | Símbolos | Símbolos adicionais (disponível em SDKs do .NET 5+) |
Símbolos de plataforma (disponíveis somente quando você especifica um TFM específico do sistema operacional) |
---|---|---|---|
.NET Framework | NETFRAMEWORK , NET48 , NET472 , NET471 , NET47 , NET462 , NET461 , NET46 , NET452 , NET451 , NET45 , NET40 , NET35 , NET20 |
NET48_OR_GREATER , NET472_OR_GREATER , NET471_OR_GREATER , NET47_OR_GREATER , NET462_OR_GREATER , NET461_OR_GREATER , NET46_OR_GREATER , NET452_OR_GREATER , NET451_OR_GREATER , NET45_OR_GREATER , NET40_OR_GREATER , NET35_OR_GREATER , NET20_OR_GREATER |
|
.NET Standard | NETSTANDARD , NETSTANDARD2_1 , NETSTANDARD2_0 , NETSTANDARD1_6 , NETSTANDARD1_5 , NETSTANDARD1_4 , NETSTANDARD1_3 , NETSTANDARD1_2 , NETSTANDARD1_1 , NETSTANDARD1_0 |
NETSTANDARD2_1_OR_GREATER , NETSTANDARD2_0_OR_GREATER , NETSTANDARD1_6_OR_GREATER , NETSTANDARD1_5_OR_GREATER , NETSTANDARD1_4_OR_GREATER , NETSTANDARD1_3_OR_GREATER , NETSTANDARD1_2_OR_GREATER , NETSTANDARD1_1_OR_GREATER , NETSTANDARD1_0_OR_GREATER |
|
.NET 5+ (e .NET Core) | NET , NET8_0 , NET7_0 , NET6_0 , NET5_0 , NETCOREAPP , NETCOREAPP3_1 , NETCOREAPP3_0 , NETCOREAPP2_2 , NETCOREAPP2_1 , NETCOREAPP2_0 , NETCOREAPP1_1 , NETCOREAPP1_0 |
NET8_0_OR_GREATER , NET7_0_OR_GREATER , NET6_0_OR_GREATER , NET5_0_OR_GREATER , NETCOREAPP3_1_OR_GREATER , NETCOREAPP3_0_OR_GREATER , NETCOREAPP2_2_OR_GREATER , NETCOREAPP2_1_OR_GREATER , NETCOREAPP2_0_OR_GREATER , NETCOREAPP1_1_OR_GREATER , NETCOREAPP1_0_OR_GREATER |
ANDROID , BROWSER , IOS , MACCATALYST , MACOS , TVOS , WINDOWS ,[OS][version] (por exemplo, IOS15_1 ),[OS][version]_OR_GREATER (por exemplo, IOS15_1_OR_GREATER ) |
Observação
- Os símbolos sem versão são definidos independentemente da versão para a qual você está direcionando.
- Os símbolos específicos à versão são definidos apenas para a versão que você está direcionando.
- Os símbolos
<framework>_OR_GREATER
são definidos para a versão que você está direcionando e todas as versões anteriores. Por exemplo, se você estiver direcionando .NET Framework 2.0, os seguintes símbolos serão definidos:NET20
,NET20_OR_GREATER
,NET11_OR_GREATER
eNET10_OR_GREATER
. - Os símbolos
NETSTANDARD<x>_<y>_OR_GREATER
são definidos apenas para destinos .NET Standard, e não para destinos que implementam o .NET Standard, como o .NET Core e o .NET Framework. - Eles são diferentes dos TFMs (Moniker da Estrutura de Destino) usados pela propriedade MSBuild
TargetFramework
e pelo NuGet.
Aqui está um exemplo fazendo uso de compilação condicional por destino:
using System;
using System.Text.RegularExpressions;
#if NET40
// This only compiles for the .NET Framework 4 targets
using System.Net;
#else
// This compiles for all other targets
using System.Net.Http;
using System.Threading.Tasks;
#endif
namespace MultitargetLib
{
public class Library
{
#if NET40
private readonly WebClient _client = new WebClient();
private readonly object _locker = new object();
#else
private readonly HttpClient _client = new HttpClient();
#endif
#if NET40
// .NET Framework 4.0 does not have async/await
public string GetDotNetCount()
{
string url = "https://www.dotnetfoundation.org/";
var uri = new Uri(url);
string result = "";
// Lock here to provide thread-safety.
lock(_locker)
{
result = _client.DownloadString(uri);
}
int dotNetCount = Regex.Matches(result, ".NET").Count;
return $"Dotnet Foundation mentions .NET {dotNetCount} times!";
}
#else
// .NET Framework 4.5+ can use async/await!
public async Task<string> GetDotNetCountAsync()
{
string url = "https://www.dotnetfoundation.org/";
// HttpClient is thread-safe, so no need to explicitly lock here
var result = await _client.GetStringAsync(url);
int dotNetCount = Regex.Matches(result, ".NET").Count;
return $"dotnetfoundation.org mentions .NET {dotNetCount} times in its HTML!";
}
#endif
}
}
Se você compilar esse projeto com dotnet build
, notará três diretórios sob a pasta bin/
:
net40/
net45/
netstandard2.0/
Cada um deles contém arquivos .dll
para cada destino.
Como testar bibliotecas no .NET
É importante ser capaz de testar em várias plataformas. Você pode usar o xUnit ou MSTest pronto para uso. Ambos são perfeitamente adequados para realizar o teste da unidade de biblioteca no .NET. A maneira de configurar sua solução com projetos de teste dependerá da estrutura da sua solução. O exemplo a seguir pressupõe que os diretórios de origem e de teste estão no mesmo diretório de nível superior.
Observação
Ele usa alguns comandos da CLI do .NET. Consulte dotnet new e dotnet sln para obter mais informações.
Configure sua solução. Você pode fazer isso usando os seguintes comandos:
mkdir SolutionWithSrcAndTest cd SolutionWithSrcAndTest dotnet new sln dotnet new classlib -o MyProject dotnet new xunit -o MyProject.Test dotnet sln add MyProject/MyProject.csproj dotnet sln add MyProject.Test/MyProject.Test.csproj
Isso criará projetos e os conectará em uma solução. O diretório para
SolutionWithSrcAndTest
deve ter esta aparência:/SolutionWithSrcAndTest |__SolutionWithSrcAndTest.sln |__MyProject/ |__MyProject.Test/
Navegue até o diretório do projeto de teste e adicione uma referência a
MyProject.Test
deMyProject
.cd MyProject.Test dotnet add reference ../MyProject/MyProject.csproj
Restaure os pacotes e compile projetos:
dotnet restore dotnet build
Verifique se o xUnit é executado por meio da execução do comando
dotnet test
. Se você optar por usar o MSTest, o executor de console do MSTest deverá ser executado.
Pronto! Agora você pode testar sua biblioteca em todas as plataformas usando as ferramentas de linha de comando. É muito simples testar sua biblioteca agora que está tudo configurado:
- Faça alterações na sua biblioteca.
- Execute os testes de linha de comando no diretório de teste com o comando
dotnet test
.
Seu código será recriado automaticamente quando você invoca o comando dotnet test
.
Como usar vários projetos
Uma necessidade comum das bibliotecas grandes é alocar funcionalidades em diferentes projetos.
Imagine que você deseja compilar uma biblioteca que poderia ser consumida em expressões idiomáticas de C# e F#. Isso significa que os consumidores da sua biblioteca o farão de maneiras naturais ao C# ou ao F#. Por exemplo, você poderia consumir a biblioteca em C# dessa forma:
using AwesomeLibrary.CSharp;
public Task DoThings(Data data)
{
var convertResult = await AwesomeLibrary.ConvertAsync(data);
var result = AwesomeLibrary.Process(convertResult);
// do something with result
}
No F#, ela poderia assemelhar-se a:
open AwesomeLibrary.FSharp
let doWork data = async {
let! result = AwesomeLibrary.AsyncConvert data // Uses an F# async function rather than C# async method
// do something with result
}
Cenários de consumo como esse significam que as APIs que estão sendo acessadas devem ter uma estrutura diferente para C# e F#. Uma abordagem comum para realizar isso é fatorar toda a lógica de uma biblioteca em um projeto principal, com projetos C# e F# definindo as camadas de API que chamam esse projeto principal. O restante da seção usará os nomes a seguir:
- AwesomeLibrary.Core – Um projeto principal que contém toda a lógica para a biblioteca
- AwesomeLibrary.CSharp – Um projeto com APIs públicas destinadas ao consumo em C#
- AwesomeLibrary.FSharp – Um projeto com APIs públicas destinadas ao consumo em F#
Você pode executar os comandos a seguir no seu terminal para produzir a mesma estrutura que este guia:
mkdir AwesomeLibrary && cd AwesomeLibrary
dotnet new sln
mkdir AwesomeLibrary.Core && cd AwesomeLibrary.Core && dotnet new classlib
cd ..
mkdir AwesomeLibrary.CSharp && cd AwesomeLibrary.CSharp && dotnet new classlib
cd ..
mkdir AwesomeLibrary.FSharp && cd AwesomeLibrary.FSharp && dotnet new classlib -lang "F#"
cd ..
dotnet sln add AwesomeLibrary.Core/AwesomeLibrary.Core.csproj
dotnet sln add AwesomeLibrary.CSharp/AwesomeLibrary.CSharp.csproj
dotnet sln add AwesomeLibrary.FSharp/AwesomeLibrary.FSharp.fsproj
Isso adicionará os três projetos acima e um arquivo de solução que os vincula. Criar o arquivo de solução e vincular projetos permitirá que você restaure e compile projetos de um nível superior.
Referência projeto a projeto
A melhor maneira de fazer referência a um projeto é usar a CLI do .NET para adicionar uma referência de projeto. Dos diretórios dos projetos AwesomeLibrary.CSharp e AwesomeLibrary.FSharp, você pode executar o seguinte comando:
dotnet add reference ../AwesomeLibrary.Core/AwesomeLibrary.Core.csproj
Os arquivos de projeto para AwesomeLibrary.CSharp e AwesomeLibrary.FSharp agora farão referência a AwesomeLibrary.Core como um destino ProjectReference
. Você pode verificar isso inspecionando os arquivos de projeto e vendo o seguinte neles:
<ItemGroup>
<ProjectReference Include="..\AwesomeLibrary.Core\AwesomeLibrary.Core.csproj" />
</ItemGroup>
Você pode adicionar esta seção a cada arquivo de projeto manualmente se preferir não usar a CLI do .NET.
Estruturar uma solução
Outro aspecto importante das soluções multiprojetos é estabelecer uma boa estrutura de projeto geral. Você pode organizar o código da maneira que desejar, e desde que vincule cada projeto ao arquivo de solução com dotnet sln add
, você poderá executar dotnet restore
e dotnet build
no nível da solução.