Diretivas de pré-processador

Sugestão

Novo no desenvolvimento de software? Não vais precisar de diretivas do pré-processador imediatamente. Foca-te primeiro nos tutoriais para começar e volta aqui quando os teus projetos exigirem compilação condicional ou configuração de build.

Experiente noutra língua? Se estiveres familiarizado com #ifdef C/C++ ou compilação condicional noutras linguagens, as diretivas de pré-processador em C# funcionam de forma semelhante. Passa rapidamente até à sintaxe que precisas.

As diretivas de pré-processador C# indicam ao compilador que código deve incluir, excluir ou tratar de forma diferente quando constrói a sua aplicação. Esta orientação pode alterar o programa resultante. As diretivas do pré-processador começam sempre com # e devem aparecer numa linha própria (ignorando o espaço em branco inicial). Pode adicionar um comentário final após a diretiva. Embora a referência linguística documente todas as diretivas disponíveis, três grupos abrangem o uso quotidiano:

  • Aplicações baseadas em ficheiros (#:) - configurar aplicações baseadas em ficheiros.
  • Compilação condicional (#if / #elif / #else / #endif) — incluir ou excluir código com base na configuração de build ou framework de destino.
  • Supressão de avisos (#pragma warning) — suprimir ou restaurar avisos específicos do compilador.

Diretivas de aplicação baseadas em ficheiros

A partir de C# 14, as aplicações baseadas em ficheiros usam duas diretivas adicionais:

  • #! — a linha shebang que permite executar o ficheiro diretamente no Unix (por exemplo, ./Program.cs). Isto requer que a permissão de execução seja definida no ficheiro (chmod +x <file>).
  • #: — diretivas do sistema de construção que configuram pacotes, definições do SDK e outras opções para programas de ficheiro único.

Uso #:package para adicionar um pacote NuGet. Por exemplo, a seguinte aplicação baseada em ficheiro usa o Spectre.Console pacote para renderizar saída formatada:

#!/usr/bin/env dotnet
#:package Spectre.Console@*

AnsiConsole.MarkupLine("[bold green]Hello[/] from a file-based app!");

Podes especificar uma versão exata com @, ou usar @* para puxar a versão mais recente. Adicione múltiplas #:package diretivas para incluir mais pacotes:

#:package Serilog@3.1.1

Outras #: diretivas permitem referenciar projetos, definir propriedades do MSBuild ou alterar o SDK:

#:project ../SharedLibrary/SharedLibrary.csproj
#:property PublishAot=false
#:sdk Microsoft.NET.Sdk.Web

Para a lista completa de diretivas, veja Aplicações baseadas em ficheiros e a referência da linguagem.

Compilação condicional

Use #if, #elif, #else, e #endif para incluir ou excluir código com base na definição de um símbolo. Os símbolos mais comuns são DEBUG (definidos automaticamente para builds de Debug) e símbolos de framework de destino, como NET10_0_OR_GREATER:

static void ConfigureLogging()
{
#if DEBUG
    Console.WriteLine("Debug logging enabled — verbose output active.");
#else
    Console.WriteLine("Release logging — errors only.");
#endif
}

O sistema de build define o símbolo DEBUG quando compilas na configuração Debug. Não precisas de o definir tu próprio. Permitir símbolos do framework como NET10_0_OR_GREATER e NET8_0_OR_GREATER permite que escrevas código que se adapta a diferentes versões do .NET em projetos de múltiplas plataformas.

Pode combinar símbolos com operadores lógicos: && (e), || (ou) e ! (não):

static void ShowPlatformInfo()
{
#if NET10_0_OR_GREATER
    Console.WriteLine("Running on .NET 10 or later.");
#elif NET8_0_OR_GREATER
    Console.WriteLine("Running on .NET 8 or 9.");
#else
    Console.WriteLine("Running on an older .NET version.");
#endif
}

Utilize #define no início de um ficheiro para definir os seus próprios símbolos. Também pode definir símbolos para todo o projeto usando a DefineConstants propriedade no seu ficheiro de projeto.

Supressão de avisos

Use #pragma warning disable para suprimir avisos específicos do compilador e #pragma warning restore para os reativar. Delimite sempre a supressão da forma mais restrita possível.

    static void ProcessData()
    {
        try
        {
            // Some operation that might fail
            var data = File.ReadAllText("config.json");
            Console.WriteLine($"Config loaded: {data.Length} characters");
        }
#pragma warning disable CS0168 // Variable is declared but never used
        catch (FileNotFoundException ex)
#pragma warning restore CS0168
        {
            // Fall back to defaults — the exception details aren't needed here
            Console.WriteLine("Config file not found, using defaults.");
        }
    }

Sugestão

Especifique sempre o número de aviso, como CS0168, em vez de desativar todos os avisos. Esta abordagem mantém a supressão direcionada e torna claro porque é que um aviso está a ser suprimido.