Desenvolver bibliotecas com a CLI do .NET
Este 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 suportado. Você ainda pode criar bibliotecas com o Visual Studio e, se essa for sua experiência preferida, consulte o guia do Visual Studio.
Pré-requisitos
Você precisa do SDK do .NET instalado em sua máquina.
Para as seções deste documento que lidam com versões do .NET Framework, você precisa do .NET Framework instalado em uma máquina Windows.
Além disso, se você deseja oferecer suporte a destinos mais antigos do .NET Framework, você precisa instalar pacotes de segmentação ou pacotes de desenvolvedor da página de downloads do .NET Framework. Consulte esta tabela:
Versão do .NET Framework | O que fazer o download |
---|---|
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 do desenvolvedor do .NET Framework 4.5.2 |
4.5.1 | Pacote do desenvolvedor do .NET Framework 4.5.1 |
4,5 | Kit de Desenvolvimento de Software do Windows para Windows 8 |
4.0 | SDK do Windows para Windows 7 e .NET Framework 4 |
2.0, 3.0 e 3.5 | Tempo de execução do .NET Framework 3.5 SP1 (ou versão Windows 8+) |
Como direcionar o .NET 5+ ou o .NET Standard
Você controla a estrutura de destino do seu projeto adicionando-a ao seu arquivo de projeto (.csproj ou .fsproj). Para obter orientação sobre como escolher entre o direcionamento do .NET 5+ ou do .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 você deseja direcionar o .NET Framework versões 4.0 ou inferiores, ou deseja usar uma API disponível no .NET Framework mas não no .NET Standard (por exemplo, System.Drawing
), leia as seções a seguir e saiba como multitarget.
Como direcionar o .NET Framework
Nota
Estas instruções pressupõem que você tenha o .NET Framework instalado em sua máquina. Consulte os Pré-requisitos para instalar dependências.
Lembre-se de que algumas das versões do .NET Framework usadas aqui não são mais suportadas. Consulte as Perguntas frequentes sobre a Política de Ciclo de Vida de Suporte do .NET Framework sobre versões sem suporte.
Se você quiser atingir o número máximo de desenvolvedores e projetos, use o .NET Framework 4.0 como seu destino de linha de base. Para direcionar o .NET Framework, comece usando o Target Framework Moniker (TFM) correto que corresponde à versão do .NET Framework que você deseja suportar.
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 TargetFramework
seção do seu arquivo de projeto. Por exemplo, veja como você escreveria uma biblioteca destinada ao .NET Framework 4.0:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net40</TargetFramework>
</PropertyGroup>
</Project>
E já está! Embora isso tenha sido compilado apenas para o .NET Framework 4, você pode usar a biblioteca em versões mais recentes do .NET Framework.
Como multitarget
Nota
As instruções a seguir pressupõem que você tenha o .NET Framework instalado em sua máquina. Consulte a seção Pré-requisitos para saber quais dependências você precisa instalar e de onde baixá-las.
Talvez seja necessário direcionar versões mais antigas do .NET Framework quando seu projeto oferece suporte ao .NET Framework e ao .NET. Nesse cenário, se você quiser usar APIs mais recentes e construções de linguagem para os destinos mais recentes, use #if
diretivas em seu código. Também pode ser necessário adicionar pacotes e dependências diferentes para cada plataforma que você está segmentando para incluir as diferentes APIs necessárias para cada caso.
Por exemplo, digamos que você tenha uma biblioteca que execute operações de rede por HTTP. Para o .NET Standard e o .NET Framework versões 4.5 ou superiores, você pode usar a HttpClient
classe do System.Net.Http
namespace. No entanto, versões anteriores do .NET Framework não têm a HttpClient
classe, então você pode usar a WebClient
System.Net
classe do namespace para esses em vez disso.
Seu arquivo de projeto pode ter esta aparência:
<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 grandes mudanças aqui:
- O
TargetFramework
nó foi substituído porTargetFrameworks
, e três TFMs são expressos no interior. - Há um
<ItemGroup>
nó para onet40
destino puxando em uma referência do .NET Framework. - Há um
<ItemGroup>
nó para onet45
destino puxando duas referências do .NET Framework.
Símbolos do pré-processador
O sistema de compilação está ciente dos seguintes símbolos de pré-processador usados em #if
diretivas:
Estruturas de destino | Símbolos | Símbolos adicionais (disponível em SDKs do .NET 5+) |
Símbolos da plataforma (disponível apenas quando você especifica um TFM específico do sistema operacional) |
---|---|---|---|
.NET Framework | NETFRAMEWORK , NET481 , , NET48 , NET472 , NET47 , NET462 NET45 NET46 NET35 NET461 NET452 NET451 NET40 NET471 NET20 |
NET48_OR_GREATER , NET472_OR_GREATER , , NET471_OR_GREATER , , NET461_OR_GREATER NET45_OR_GREATER NET46_OR_GREATER NET35_OR_GREATER NET462_OR_GREATER NET452_OR_GREATER NET451_OR_GREATER NET40_OR_GREATER NET47_OR_GREATER NET20_OR_GREATER |
|
.NET Standard | NETSTANDARD , NETSTANDARD2_1 , , NETSTANDARD2_0 , NETSTANDARD1_6 , NETSTANDARD1_4 NETSTANDARD1_5 , 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_3_OR_GREATER NETSTANDARD1_4_OR_GREATER , NETSTANDARD1_2_OR_GREATER , NETSTANDARD1_1_OR_GREATER ,NETSTANDARD1_0_OR_GREATER |
|
.NET 5+ (e .NET Core) | NET , NET9_0 , , NET8_0 , , NET5_0 NETCOREAPP3_0 NET6_0 NETCOREAPP3_1 NETCOREAPP1_1 NETCOREAPP NETCOREAPP2_2 NETCOREAPP2_1 NETCOREAPP2_0 NET7_0 NETCOREAPP1_0 |
NET8_0_OR_GREATER , NET7_0_OR_GREATER , , NET5_0_OR_GREATER NET6_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 , MACOS MACCATALYST , TVOS , WINDOWS ,[OS][version] (por exemplo IOS15_1 ),[OS][version]_OR_GREATER (por exemplo IOS15_1_OR_GREATER ) |
Nota
- Os símbolos sem versão são definidos independentemente da versão que você está segmentando.
- Os símbolos específicos da versão são definidos apenas para a versão que você está segmentando.
- Os
<framework>_OR_GREATER
símbolos são definidos para a versão que você está segmentando e todas as versões anteriores. Por exemplo, se você estiver direcionando o .NET Framework 2.0, os seguintes símbolos serão definidos:NET20
,NET20_OR_GREATER
,NET11_OR_GREATER
eNET10_OR_GREATER
. - Os
NETSTANDARD<x>_<y>_OR_GREATER
símbolos são definidos apenas para destinos .NET Standard e não para destinos que implementam o .NET Standard, como .NET Core e .NET Framework. - Eles são diferentes dos monikers de estrutura de destino (TFMs) usados pela propriedade MSBuild
TargetFramework
e NuGet.
Aqui está um exemplo que faz uso da 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 este projeto com dotnet build
o , notará três diretórios na bin/
pasta:
net40/
net45/
netstandard2.0/
Cada um deles contém os .dll
arquivos para cada destino.
Como testar bibliotecas no .NET
É importante poder testar em todas as plataformas. Você pode usar xUnit ou MSTest fora da caixa. Ambos são perfeitamente adequados para testar sua biblioteca no .NET. A forma como você configura 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 teste e de origem vivam no mesmo diretório de nível superior.
Nota
Isso usa alguns comandos da CLI do .NET. Consulte dotnet new e dotnet sln para obter mais informações.
Configure a sua solução. Você pode fazer isso com 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. Seu 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 pacotes e construa projetos:
dotnet restore dotnet build
Verifique se o xUnit é executado executando o
dotnet test
comando. Se você optar por usar o MSTest, o executor do console MSTest deverá ser executado.
E já está! Agora você pode testar sua biblioteca em todas as plataformas usando ferramentas de linha de comando. Para continuar a testar agora que tem tudo configurado, testar a sua biblioteca é muito simples:
- Faça alterações na sua biblioteca.
- Execute testes a partir da linha de comando, no diretório de teste, com
dotnet test
o comando.
Seu código será reconstruído automaticamente quando você invocar dotnet test
o comando.
Como usar vários projetos
Uma necessidade comum de bibliotecas maiores é colocar a funcionalidade em diferentes projetos.
Imagine que você deseja construir uma biblioteca que possa ser consumida em C# e F# idiomáticos. Isso significaria que os consumidores de sua biblioteca a consomem de maneiras naturais para C# ou F#. Por exemplo, em C# você pode consumir a biblioteca assim:
using AwesomeLibrary.CSharp;
public Task DoThings(Data data)
{
var convertResult = await AwesomeLibrary.ConvertAsync(data);
var result = AwesomeLibrary.Process(convertResult);
// do something with result
}
Em F#, pode ter esta aparência:
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 acessadas precisam ter uma estrutura diferente para C# e F#. Uma abordagem comum para fazer 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 seguintes nomes:
- 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 seguintes comandos em seu terminal para produzir a mesma estrutura deste 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 construa projetos a partir de um nível superior.
Referência de 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. Nos diretórios de projeto 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 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.
Estruturação de uma solução
Outro aspeto importante das soluções multi-projeto é estabelecer uma boa estrutura geral do projeto. Você pode organizar o código como quiser, e desde que vincule cada projeto ao seu arquivo de solução com dotnet sln add
o , você poderá executar dotnet restore
e dotnet build
no nível da solução.