Diretivas do pré-processador

Dica

Novo no desenvolvimento de software? Você não precisará de diretivas de pré-processador imediatamente. Concentre-se nos tutoriais de Introdução primeiro e volte aqui quando seus projetos exigirem compilação condicional ou configuração de build.

Experimentou em outro idioma? Se você estiver familiarizado com #ifdef no C/C++ ou compilação condicional em outras linguagens de programação, as diretivas de pré-processador do C# funcionam de forma similar. Avance para a sintaxe que você precisa.

As diretivas de pré-processador C# informam ao compilador qual código incluir, excluir ou tratar de forma diferente quando ele compila seu aplicativo. Essas diretrizes podem alterar o programa resultante. As diretivas de pré-processador sempre começam # e devem aparecer em sua própria linha (ignorando o espaço em branco à esquerda). Você pode adicionar um comentário ao final após a diretiva. Embora a referência de linguagem documente todas as diretivas disponíveis, três grupos cobrem o uso diário:

  • Aplicativos baseados em arquivo (#:) – configurar aplicativos baseados em arquivo.
  • Compilação condicional (#if / #elif / #else / #endif) – incluir ou excluir código com base na configuração de build ou na estrutura de destino.
  • Supressão de aviso (#pragma warning) – suprimir ou restaurar avisos específicos do compilador.

Diretivas de aplicativo baseadas em arquivo

A partir do C# 14, os aplicativos baseados em arquivo usam duas diretivas adicionais:

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

Use #:package para adicionar um pacote NuGet. Por exemplo, o seguinte aplicativo baseado em arquivos usa o pacote Spectre.Console para renderizar a saída estilizada:

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

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

Você pode especificar uma versão exata com @ou usar @* para efetuar pull da versão mais recente. Adicione várias #: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 obter a lista completa de diretivas, consulte aplicativos baseados em arquivo e a referência de idioma.

Compilação condicional

Use #if, #elif, #else e #endif para incluir ou excluir código com base em se um símbolo está definido. Os símbolos mais comuns são DEBUG (definidos automaticamente para builds de depuração) e símbolos de estrutura 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 a compilação é feita na configuração de Depuração. Você não precisa defini-lo por conta própria. Símbolos de estrutura de destino, como NET10_0_OR_GREATER e NET8_0_OR_GREATER, permitem que você escreva um código que possa se adaptar a diferentes versões do .NET em projetos com múltiplos destinos.

Você 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
}

Use #define na parte superior de um arquivo para definir seus próprios símbolos. Você também pode definir símbolos para todo o projeto usando a DefineConstants propriedade em seu arquivo de projeto.

Supressão de aviso

Use #pragma warning disable para suprimir avisos específicos do compilador e #pragma warning restore habilitá-los novamente. Sempre delimite a supressão o mais estritamente 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.");
        }
    }

Dica

Sempre especifique o número de aviso, como CS0168, em vez de desabilitar todos os avisos. Essa abordagem mantém a supressão direcionada e deixa claro por que um aviso está sendo suprimido.