Propriedades especiais em projetos .NET
Arquivos de projeto .NET
Certas configurações em projetos .NET podem modificar como o projeto é compilado, e a performance e tamanho dos programas publicados.
Essas configurações ficam nos arquivos .csproj (C#), .fsproj (F#) e .vbproj (VB.NET); também podem ser passadas via linha de comando, ao executar dotnet run
, dotnet build
e dotnet publish
.
Em arquivos de projeto
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
Via linha de comando
dotnet publish `
--configuration Release `
--self-contained true `
--runtime "linux-x64" `
-p:PublishSingleFile=true `
--output "./out/"
Propriedades
- TargetFramework
- Nullable
- ImplicitUsings
- TreatWarningsAsErrors
- Configuration
- SelfContained
- PublishSingleFile
- EnableCompressionInSingleFile
- PublishReadyToRun
- Trimming
- NativeAOT
- Version
- ApplicationIcon
TargetFramework
Diz para quais versões do .NET seu projeto deve compilar. Geralmente, especifica-se apenas um TFM (target framework moniker), porém, se precisar atender a mais de uma versão do .NET, você pode especificar uma lista de TFMs, separados por ponto-e-vírgula (';').
<PropertyGroup>
<!-- apenas um -->
<TargetFramework>net8.0</TargetFramework>
<!-- múltiplos. note o plural. -->
<TargetFrameworks>netstandard1.4;net40;net45</TargetFrameworks>
</PropertyGroup>
Nullable
Quando presente com valor true, avisa sempre que uma variável que pode ser nula for usada em um local que não aceita nulos.
Considere o código abaixo. Há um aviso de que um valor nulo pode acabar sendo passado para o método int.Parse()
, método este que não aceita strings nulas.
Há quatro formas de resolver esse aviso:
- Alterar a assinatura do método. Quando uma variável tem interrogação após o tipo (
string?
), significa que aceita nulos; sem interrogação (string
), espera-se um valor não-nulo.
public int ConverterParaInt(string stringNumerica) =>
int.Parse(stringNumerica);
- Proteger via código contra valores nulos.
public int ConverterParaInt(string? stringNumerica)
{
if (stringNumerica == null)
throw new ArgumentNullException(nameof(stringNumerica));
else
return int.Parse(stringNumerica);
}
- Marcar com uma exclamação. Ela indica que o valor é seguro.
public int ConverterParaInt(string? stringNumerica) =>
int.Parse(stringNumerica!);
- Desabilitar a verificação de nuláveis via diretiva de compilação.
#nullable disable warnings
public int ConverterParaInt(string? stringNumerica) =>
int.Parse(stringNumerica);
#nullable restore warnings
ImplicitUsings
Faz com que todos os arquivos de código (.cs, .fs, .vb) venham com alguns usings declarados implicitamente, de modo que não é necessário declarar manualmente.
A lista de namespaces varia conforme o tipo de projeto.
Para projetos console e class libraries:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
Para projetos Web, além dos listados acima:
using System.Net.Http.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
Para Workers, a primeira lista, mais:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
TreatWarningsAsErrors
Faz com que avisos sejam considerados erros e assim o projeto não compile se houver algum aviso.
<PropertyGroup>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
</PropertyGroup>
É uma opção interessante para melhorar a qualidade do código, pois força os desenvolvedores a se atentarem aos avisos, que indicam possíveis falhas de lógica e tratamento de dados. Um exemplo é combinar essa opção junto com o Nullable.
Comentário do autor: apenas isso como medida não tornará seu código melhor. Os devs podem simplesmente contornar os avisos, por exemplo, através de
#pragma warning disable
ou ignorar nuláveis via exclamação (ver acima). Um código de qualidade é resultado de boas práticas de programação e revisões por pares.
Configuration
Determina se o projeto vai ser compilado como Debug ou Release.
Em modo Debug, a compilação produz arquivos de símbolos (.pdb) que facilitam o debug do código via IDE e breakpoints. Além disso, o código não é optimizado plenamente.
No modo Release, a compilação não produz arquivos de símbolos e a optimização do código é total. Sempre que for gerar um programa para publicação, deve-se usar a configuração Release.
Essa opção é passada via flags.
SelfContained
Embute o .NET runtime junto ao executável publicado. Isso significa que a máquina de destino (onde o programa vai rodar) não precisará ter ele instalado.
Essa opção faz com que o tamanho do executável final seja maior (por ter o .NET runtime consigo).
PublishSingleFile
Condensa as várias DLLs e arquivos auxiliares do programa em um único arquivo executável, diminuindo a quantidade de arquivos publicados. Isso é interessante para programas portáteis (portable).
DLLs interop em C/C++ são imunes a essa opção e vão aparecer em arquivos separados.
<PropertyGroup>
<PublishSingleFile>True</PublishSingleFile>
</PropertyGroup>
EnableCompressionInSingleFile
Junto com a opção acima, comprime o executável condensado, diminuindo seu tamanho no disco rígido. Um contraponto é que a inicialização do programa fica mais lenta, pois ele precisa descomprimir seu conteúdo antes de executar. (Ele não cria arquivos ao executar; a descompressão é feita em memória.)
<PropertyGroup>
<EnableCompressionInSingleFile>True</EnableCompressionInSingleFile>
</PropertyGroup>
PublishReadyToRun
É uma optimização especial do compilador que torna a inicialização do programa mais rápida, prevendo como ele vai executar na máquina de destino e trocando partes de código intermedíario (IL, intermediate language) por código de máquina (Assembly). Isso é interessante para APIs, por exemplo, pois assim elas demoram menos tempo para entrar em funcionamento.
Essa opção aumenta o tamanho do executável final e aumenta o tempo de compilação.
<PropertyGroup>
<PublishReadyToRun>True</PublishReadyToRun>
</PropertyGroup>
Trimming
Recorta partes não utilizadas do código e de bibliotecas do programa, diminuindo significativamente o tamanho do executável final.
Essa opção deve ser usada com cuidado, pois pode remover partes importantes do código e causar erros durante a execução. Para evitar esses problemas, o compilador emite avisos em linhas suscetíveis de serem recortadas, para que o programador adote alternativas ou então marque o código para não ser removido.
O recorte pode ser parcial, ou seja, só em classes e bibliotecas especificadas, ou então no programa inteiro.
Esse tema é extenso e requer uma leitura com atenção, por isso, recomendo a documentação.
Exemplo de trimming parcial:
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
</PropertyGroup>
<!-- lista de assemblies que serão trimmados -->
<ItemGroup>
<TrimmableAssembly Include="MyAssembly" />
</ItemGroup>
NativeAOT
NativeAOT é um tipo especial de compilação direto para linguagem de máquina, aumentando a performance do programa.
Na compilação tradicional, o executável final é composto pelo código em linguagem intermediária (IL, intermediate language) que é interpretado pelo .NET runtime (CoreCLR). Durante a execução do programa, o runtime traduz cada instrução em IL para código de máquina, e assim o programa roda.
Ao compilar diretamente para linguagem nativa, o intermediário é removido e pode-se aproveitar diversas optimizações específicas de arquitetura de processador e sistema operacional, de modo que:
- a execução fica mais rápida,
- consome menos memória RAM,
- o tamanho do executável final é menor.
Essa opção de compilação não é simples de usar, contudo. Ela requer que todos os caminhos de código possam ser analisados estaticamente, em outras palavras, não é possível usar reflection no programa, o que afeta por exemplo a serialização e desserialização de JSON e XML.
Para (des)serialização de JSON, uma solução é os source generators do System.Text.Json, que escrevem e lêem JSONs através de código gerado em tempo de compilação.
WPF e Windows Forms são baseados em reflection e por isso não suportam NativeAOT; mas aplicações console e ASP.NET minimal APIs têm suporte ao NativeAOT.
Assim como o trimming, usar essa funcionalidade requer cuidado e vale ler a documentação oficial.
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
Version
Marca qual é a versão do programa, do arquivo ou do assembly.
<PropertyGroup>
<Version>3.7.1</Version>
<!-- o valor de Version é reutilizado abaixo -->
<FileVersion>$(Version)</FileVersion>
<AssemblyVersion>$(Version)</AssemblyVersion>
</PropertyGroup>
Para pegar a versão via código:
string versionName = Assembly.GetExecutingAssembly()
.GetName()
.Version?
.ToString(3);
// versionName = "3.7.1"
Aviso: o versionamento de pacotes NuGet é feito através de outras propriedades. Confira mais na documentação.
ApplicationIcon
Coloca um ícone no programa para exibição no Windows Explorer.
<PropertyGroup>
<ApplicationIcon>my_program.ico</ApplicationIcon>
</PropertyGroup>
Fontes e leituras interessantes
- .NET Docs - Target frameworks in SDK-style projects
- Microsoft DevBlogs - Embracing nullable reference types
- DotNetCore Tutorials - Implicit Using Statements In .NET 6
- .NET Docs - Single-file deployment
- .NET Docs - .NET application publishing overview
- .NET Docs - ReadyToRun Compilation
- .NET Docs - Trimming options
- .NET Docs - Native AOT deployment
- .NET Docs - How to use source generation in System.Text.Json
- GitHub Dotnet Core repo - How to set application icon on Windows?
- .NET Docs - NuGet Package authoring best practices
- .NET Docs - MSBuild reference for .NET SDK projects
- MSBuild Docs - Common MSBuild project properties
- MSBuild Docs - MSBuild reserved and well-known properties
Campinas/SP,
Brasil