Partilhar via


Diretivas de pré-processador C#

Embora o compilador não tenha um pré-processador separado, as diretivas descritas nesta seção são processadas como se existisse. Você os usa para ajudar na compilação condicional. Ao contrário das diretivas C e C++, você não pode usar essas diretivas para criar macros. Uma diretiva de pré-processador deve ser a única instrução em uma linha.

Contexto anulável

A #nullable diretiva de pré-processador define o contexto de anotação anulável e o contexto de aviso anulável. Esta diretiva controla se as anotações anuláveis têm efeito e se são dados avisos de anulabilidade. Cada contexto está desativado ou habilitado.

Ambos os contextos podem ser especificados no nível do projeto (fora do código-fonte C#) adicionando o Nullable elemento ao PropertyGroup elemento . A #nullable diretiva controla os contextos de anotação e aviso e tem precedência sobre as configurações no nível do projeto. Uma diretiva define o(s) contexto(s) que controla até que outra diretiva a substitua ou até o final do arquivo de origem.

O efeito das diretivas é o seguinte:

  • #nullable disable: Define a anotação anulável e os contextos de aviso como desativados.
  • #nullable enable: Define a anotação anulável e os contextos de aviso como habilitados.
  • #nullable restore: Restaura a anotação anulável e os contextos de aviso para as configurações do projeto.
  • #nullable disable annotations: Define o contexto de anotação anulável como desativado.
  • #nullable enable annotations: Define o contexto de anotação anulável como habilitado.
  • #nullable restore annotations: Restaura o contexto de anotação anulável para as configurações do projeto.
  • #nullable disable warnings: Define o contexto de aviso anulável como desativado.
  • #nullable enable warnings: Define o contexto de aviso anulável como habilitado.
  • #nullable restore warnings: Restaura o contexto de aviso anulável para as configurações do projeto.

Compilação condicional

Você usa quatro diretivas de pré-processador para controlar a compilação condicional:

  • #if: Abre uma compilação condicional, onde o código é compilado somente se o símbolo especificado estiver definido.
  • #elif: Fecha a compilação condicional anterior e abre uma nova compilação condicional com base em se o símbolo especificado está definido.
  • #else: Fecha a compilação condicional anterior e abre uma nova compilação condicional se o símbolo especificado anteriormente não estiver definido.
  • #endif: Fecha a compilação condicional anterior.

O compilador C# compila o código entre a diretiva e #endif a #if diretiva somente se o símbolo especificado estiver definido, ou não definido quando o ! operador not for usado. Ao contrário de C e C++, um valor numérico a um símbolo não pode ser atribuído. A #if instrução em C# é booleana e testa apenas se o símbolo foi definido ou não. Por exemplo, o código a seguir é compilado quando DEBUG é definido:

#if DEBUG
    Console.WriteLine("Debug version");
#endif

O código a seguir é compilado quando MYTEST não está definido:

#if !MYTEST
    Console.WriteLine("MYTEST is not defined");
#endif

Você pode usar os operadores == (igualdade) e != (desigualdade) para testar os bool valores true ou false. true significa que o símbolo está definido. A afirmação #if DEBUG tem o mesmo significado que #if (DEBUG == true). Você pode usar os && operadores (e),|| (ou) e ! (não) para avaliar se vários símbolos foram definidos. Você também pode agrupar símbolos e operadores entre parênteses.

A seguir está uma diretiva complexa que permite que seu código aproveite os recursos mais recentes do .NET enquanto permanece compatível com versões anteriores. Por exemplo, imagine que você está usando um pacote NuGet em seu código, mas o pacote só suporta .NET 6 e superior, bem como .NET Standard 2.0 e superior:

#if (NET6_0_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
    Console.WriteLine("Using .NET 6+ or .NET Standard 2+ code.");
#else
    Console.WriteLine("Using older code that doesn't support the above .NET versions.");
#endif

#if, juntamente com as #elsediretivas , #elif, #endif, #define, e #undef permite incluir ou excluir código com base na existência de um ou mais símbolos. A compilação condicional pode ser útil ao compilar código para uma compilação de depuração ou ao compilar para uma configuração específica.

Uma diretiva condicional que comece por uma #if diretiva deve ser explicitamente terminada com uma #endif diretiva. #define permite definir um símbolo. Ao usar o símbolo como a expressão passada para a #if diretiva, a expressão avalia como true. Você também pode definir um símbolo com a opção de compilador DefineConstants. Você pode desdefinir um símbolo com #undef. O escopo de um símbolo criado com #define é o arquivo no qual ele foi definido. Um símbolo que você define com DefineConstants ou com #define não entra em conflito com uma variável do mesmo nome. Ou seja, um nome de variável não deve ser passado para uma diretiva de pré-processador, e um símbolo só pode ser avaliado por uma diretiva de pré-processador.

#elif Permite criar uma diretiva condicional composta. A #elif expressão será avaliada se nem as expressões diretivas anteriores #if nem as anteriores, opcionais, #elif avaliarem a true. Se uma #elif expressão for avaliada como true, o compilador avaliará todo o código entre a diretiva condicional e a #elif próxima. Por exemplo:

#define VC7
//...
#if DEBUG
    Console.WriteLine("Debug build");
#elif VC7
    Console.WriteLine("Visual Studio 7");
#endif

#else permite criar uma diretiva condicional composta, de modo que, se nenhuma das expressões nas diretivas anteriores #if ou (opcionais) #elif for avaliada como true, o compilador avaliará todo o código entre #else e o próximo #endif. #endif(#endif) deve ser a próxima diretiva de pré-processador após #else.

#endif especifica o fim de uma diretiva condicional, que começou com a #if diretiva.

O sistema de compilação também está ciente de símbolos de pré-processador predefinidos que representam diferentes estruturas de destino em projetos no estilo SDK. Eles são úteis ao criar aplicativos que podem direcionar mais de uma versão do .NET.

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, NET48, , NET472, , NET462NET452NET47NET46NET35NET461NET451NET45NET40NET471NET20 NET48_OR_GREATER, NET472_OR_GREATER, , NET471_OR_GREATER, , NET461_OR_GREATERNET45_OR_GREATERNET46_OR_GREATERNET35_OR_GREATERNET462_OR_GREATERNET452_OR_GREATERNET451_OR_GREATERNET40_OR_GREATERNET47_OR_GREATERNET20_OR_GREATER
.NET Standard NETSTANDARD, NETSTANDARD2_1, , NETSTANDARD2_0, NETSTANDARD1_6, NETSTANDARD1_4NETSTANDARD1_5, NETSTANDARD1_3, NETSTANDARD1_2NETSTANDARD1_1,NETSTANDARD1_0 NETSTANDARD2_1_OR_GREATER, NETSTANDARD2_0_OR_GREATER, NETSTANDARD1_6_OR_GREATER, , NETSTANDARD1_5_OR_GREATER, NETSTANDARD1_3_OR_GREATERNETSTANDARD1_4_OR_GREATER, NETSTANDARD1_2_OR_GREATER, NETSTANDARD1_1_OR_GREATER,NETSTANDARD1_0_OR_GREATER
.NET 5+ (e .NET Core) NET, NET8_0, , NET7_0, , NETCOREAPPNETCOREAPP2_1NETCOREAPP3_1NETCOREAPP1_1NET5_0NETCOREAPP3_0NETCOREAPP2_2NETCOREAPP2_0NET6_0NETCOREAPP1_0 NET8_0_OR_GREATER, NET7_0_OR_GREATER, , NET5_0_OR_GREATERNET6_0_OR_GREATER, NETCOREAPP3_1_OR_GREATER, NETCOREAPP3_0_OR_GREATER, NETCOREAPP2_2_OR_GREATER, NETCOREAPP2_1_OR_GREATERNETCOREAPP2_0_OR_GREATERNETCOREAPP1_1_OR_GREATERNETCOREAPP1_0_OR_GREATER ANDROID, BROWSER, , IOS, MACOSMACCATALYST, 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_GREATERe NET10_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.

Nota

Para projetos tradicionais, não no estilo SDK, você precisa configurar manualmente os símbolos de compilação condicional para as diferentes estruturas de destino no Visual Studio por meio das páginas de propriedades do projeto.

Outros símbolos predefinidos incluem as constantes e DEBUG TRACE . Você pode substituir os valores definidos para o projeto usando #define. O símbolo DEBUG, por exemplo, é definido automaticamente dependendo das propriedades de configuração da compilação ("modo Debug" ou "Release").

O exemplo a seguir mostra como definir um MYTEST símbolo em um arquivo e, em seguida, testar os valores dos MYTEST símbolos e DEBUG . A saída deste exemplo depende se você criou o projeto no modo de configuração Debug ou Release .

#define MYTEST
using System;
public class MyClass
{
    static void Main()
    {
#if (DEBUG && !MYTEST)
        Console.WriteLine("DEBUG is defined");
#elif (!DEBUG && MYTEST)
        Console.WriteLine("MYTEST is defined");
#elif (DEBUG && MYTEST)
        Console.WriteLine("DEBUG and MYTEST are defined");
#else
        Console.WriteLine("DEBUG and MYTEST are not defined");
#endif
    }
}

O exemplo a seguir mostra como testar diferentes estruturas de destino para que você possa usar APIs mais recentes quando possível:

public class MyClass
{
    static void Main()
    {
#if NET40
        WebClient _client = new WebClient();
#else
        HttpClient _client = new HttpClient();
#endif
    }
    //...
}

Definição de símbolos

Use as duas diretivas de pré-processador a seguir para definir ou desdefinir símbolos para compilação condicional:

  • #define: Defina um símbolo.
  • #undef: Undefine um símbolo.

Você usa #define para definir um símbolo. Quando você usa o símbolo como a expressão que é passada para a #if diretiva, a expressão será avaliada para true, como mostra o exemplo a seguir:

#define VERBOSE

#if VERBOSE
   Console.WriteLine("Verbose output version");
#endif

Nota

Em C#, constantes primitivas devem ser definidas usando a const palavra-chave. Uma const declaração cria um static membro que não pode ser modificado em tempo de execução. A #define diretiva não pode ser usada para declarar valores constantes como normalmente é feito em C e C++. Se você tiver várias dessas constantes, considere criar uma classe "Constantes" separada para mantê-las.

Símbolos podem ser usados para especificar condições para compilação. Você pode testar o símbolo com um ou #if #elif. Você também pode usar o para executar a ConditionalAttribute compilação condicional. Você pode definir um símbolo, mas não pode atribuir um valor a um símbolo. A #define diretiva deve aparecer no arquivo antes de usar quaisquer instruções que não sejam também diretivas de pré-processador. Você também pode definir um símbolo com a opção de compilador DefineConstants. Você pode desdefinir um símbolo com #undef.

Definição de regiões

Você pode definir regiões de código que podem ser recolhidas em uma estrutura de tópicos usando as duas diretivas de pré-processador a seguir:

  • #region: Inicie uma região.
  • #endregion: Termine uma região.

#region Permite especificar um bloco de código que pode ser expandido ou recolhido ao usar o recurso Estrutura de Tópicos do Editor de Códigos. Em arquivos de código mais longos, é conveniente recolher ou ocultar uma ou mais regiões para que você possa se concentrar na parte do arquivo na qual está trabalhando no momento. O exemplo a seguir mostra como definir uma região:

#region MyClass definition
public class MyClass
{
    static void Main()
    {
    }
}
#endregion

Um #region bloco deve ser encerrado com uma #endregion diretiva. Um #region bloco não pode sobrepor-se a um #if bloco. No entanto, um #region bloco pode ser aninhado em um #if bloco e um #if bloco pode ser aninhado em um #region bloco.

Informações de erro e aviso

Você instrui o compilador a gerar erros e avisos do compilador definidos pelo usuário e controlar informações de linha usando as seguintes diretivas:

  • #error: Gere um erro de compilador com uma mensagem especificada.
  • #warning: Gere um aviso do compilador, com uma mensagem específica.
  • #line: Altere o número da linha impresso com as mensagens do compilador.

#error permite gerar um erro CS1029 definido pelo usuário a partir de um local específico em seu código. Por exemplo:

#error Deprecated code in this method.

Nota

O compilador trata de uma maneira especial e relata um erro do #error version compilador, CS8304, com uma mensagem contendo o compilador usado e versões de idioma.

#warning permite gerar um aviso de compilador de nível um CS1030 a partir de um local específico em seu código. Por exemplo:

#warning Deprecated code in this method.

#line Permite modificar a numeração de linha do compilador e (opcionalmente) a saída do nome do arquivo para erros e avisos.

O exemplo a seguir mostra como relatar dois avisos associados a números de linha. A #line 200 diretiva força o número da próxima linha a ser 200 (embora o padrão seja #6), e até a próxima #line diretiva, o nome do arquivo será relatado como "Especial". A #line default diretiva retorna a numeração de linha para sua numeração padrão, que conta as linhas que foram renumeradas pela diretiva anterior.

class MainClass
{
    static void Main()
    {
#line 200 "Special"
        int i;
        int j;
#line default
        char c;
        float f;
#line hidden // numbering not affected
        string s;
        double d;
    }
}

A compilação produz a seguinte saída:

Special(200,13): warning CS0168: The variable 'i' is declared but never used
Special(201,13): warning CS0168: The variable 'j' is declared but never used
MainClass.cs(9,14): warning CS0168: The variable 'c' is declared but never used
MainClass.cs(10,15): warning CS0168: The variable 'f' is declared but never used
MainClass.cs(12,16): warning CS0168: The variable 's' is declared but never used
MainClass.cs(13,16): warning CS0168: The variable 'd' is declared but never used

A #line diretiva pode ser usada em uma etapa intermediária automatizada no processo de construção. Por exemplo, se as linhas foram removidas do arquivo de código-fonte original, mas você ainda queria que o compilador gerasse saída com base na numeração de linha original no arquivo, você poderia remover linhas e, em seguida, simular a numeração de linha original com #line.

A #line hidden diretiva oculta as linhas sucessivas do depurador, de modo que, quando o desenvolvedor percorre o código, quaisquer linhas entre uma #line hidden diretiva e a próxima #line (supondo que não seja outra #line hidden diretiva) serão substituídas. Essa opção também pode ser usada para permitir que ASP.NET diferenciem entre código definido pelo usuário e gerado por máquina. Embora ASP.NET seja o principal consumidor desse recurso, é provável que mais geradores de fontes façam uso dele.

Uma #line hidden diretiva não afeta nomes de arquivo ou números de linha no relatório de erros. Ou seja, se o compilador encontrar um erro em um bloco oculto, o compilador relatará o nome do arquivo atual e o número da linha do erro.

A #line filename diretiva especifica o nome do arquivo que você deseja que apareça na saída do compilador. Por padrão, o nome real do arquivo de código-fonte é usado. O nome do arquivo deve estar entre aspas duplas ("") e deve ser precedido por um número de linha.

A partir do C# 10, você pode usar uma nova forma da #line diretiva:

#line (1, 1) - (5, 60) 10 "partial-class.cs"
/*34567*/int b = 0;

Os componentes deste formulário são:

  • (1, 1): A linha inicial e a coluna do primeiro caractere na linha que segue a diretiva. Neste exemplo, a próxima linha seria relatada como linha 1, coluna 1.
  • (5, 60): A linha final e a coluna da região marcada.
  • 10: O deslocamento da coluna para que a #line diretiva entre em vigor. Neste exemplo, a 10ª coluna seria relatada como coluna um. É aí que começa a declaração int b = 0; . Este campo é opcional. Se omitida, a diretiva produz efeitos na primeira coluna.
  • "partial-class.cs": O nome do arquivo de saída.

O exemplo anterior geraria o seguinte aviso:

partial-class.cs(1,5,1,6): warning CS0219: The variable 'b' is assigned but its value is never used

Após o remapeamento, a variável, b, está na primeira linha, no caractere seis, do arquivo partial-class.cs.

As linguagens específicas do domínio (DSLs) normalmente usam esse formato para fornecer um melhor mapeamento do arquivo de origem para a saída C# gerada. O uso mais comum desta diretiva estendida #line é remapear avisos ou erros que aparecem em um arquivo gerado para a fonte original. Por exemplo, considere esta página de barbear:

@page "/"
Time: @DateTime.NowAndThen

A propriedade DateTime.Now foi digitada incorretamente como DateTime.NowAndThen. O C# gerado para este trecho de lâmina de barbear tem a seguinte aparência, em page.g.cs:

  _builder.Add("Time: ");
#line (2, 6) - (2, 27) 15 "page.razor"
  _builder.Add(DateTime.NowAndThen);

A saída do compilador para o trecho anterior é:

page.razor(2, 2, 2, 27)error CS0117: 'DateTime' does not contain a definition for 'NowAndThen'

A linha 2, coluna 6, é onde page.razor o texto @DateTime.NowAndThen começa. Isso é observado na (2, 6) diretiva. Esse vão termina @DateTime.NowAndThen na linha 2, coluna 27. Isso é observado pela (2, 27) diretiva. O texto para DateTime.NowAndThen começa na coluna 15 de page.g.cs. Isso é observado pela 15 diretiva. Juntando todos os argumentos, o compilador relata o erro em sua localização em page.razor. O desenvolvedor pode navegar diretamente para o erro em seu código-fonte, não para a fonte gerada.

Para ver mais exemplos desse formato, consulte a especificação do recurso na seção sobre exemplos.

Pragmas

#pragma dá ao compilador instruções especiais para a compilação do arquivo no qual ele aparece. As instruções devem ser suportadas pelo compilador. Em outras palavras, você não pode usar #pragma para criar instruções de pré-processamento personalizadas.

#pragma pragma-name pragma-arguments

Onde pragma-name está o nome de um pragma reconhecido e pragma-arguments são os argumentos específicos do pragma.

#pragma advertência

#pragma warning pode ativar ou desativar determinados avisos.

#pragma warning disable warning-list
#pragma warning restore warning-list

Onde warning-list é uma lista separada por vírgulas de números de aviso. O prefixo "CS" é opcional. Quando nenhum número de aviso é especificado, disable desativa todos os avisos e restore habilita todos os avisos.

Nota

Para localizar números de aviso no Visual Studio, crie seu projeto e procure os números de aviso na janela Saída .

O disable entra em vigor a partir da próxima linha do arquivo de origem. O aviso é restaurado na linha seguinte ao restore. Se não houver nenhum restore no arquivo, os avisos serão restaurados para seu estado padrão na primeira linha de quaisquer arquivos posteriores na mesma compilação.

// pragma_warning.cs
using System;

#pragma warning disable 414, CS3021
[CLSCompliant(false)]
public class C
{
    int i = 1;
    static void Main()
    {
    }
}
#pragma warning restore CS3021
[CLSCompliant(false)]  // CS3021
public class D
{
    int i = 1;
    public static void F()
    {
    }
}

#pragma soma de verificação

Gera somas de verificação para arquivos de origem para ajudar na depuração de páginas ASP.NET.

#pragma checksum "filename" "{guid}" "checksum bytes"

Onde "filename" é o nome do arquivo que requer monitoramento para alterações ou atualizações, "{guid}" é o GUID (Identificador Global Exclusivo) para o algoritmo de hash e "checksum_bytes" é a cadeia de caracteres de dígitos hexadecimais que representam os bytes da soma de verificação. Deve ser um número par de dígitos hexadecimais. Um número ímpar de dígitos resulta em um aviso em tempo de compilação, e a diretiva é ignorada.

O depurador do Visual Studio usa uma soma de verificação para garantir que ele sempre encontre a fonte correta. O compilador calcula a soma de verificação para um arquivo de origem e, em seguida, emite a saída para o arquivo de banco de dados de programa (PDB). Em seguida, o depurador usa o PDB para comparar com a soma de verificação que ele calcula para o arquivo de origem.

Essa solução não funciona para projetos ASP.NET, porque a soma de verificação computada é para o arquivo de origem gerado, em vez do arquivo .aspx. Para resolver esse problema, #pragma checksum fornece suporte de soma de verificação para páginas ASP.NET.

Quando você cria um projeto ASP.NET no Visual C#, o arquivo de origem gerado contém uma soma de verificação para o arquivo .aspx, a partir do qual a fonte é gerada. Em seguida, o compilador grava essas informações no arquivo PDB.

Se o compilador não encontrar uma #pragma checksum diretiva no arquivo, ele calculará a soma de verificação e gravará o valor no arquivo PDB.

class TestClass
{
    static int Main()
    {
        #pragma checksum "file.cs" "{406EA660-64CF-4C82-B6F0-42D48172A799}" "ab007f1d23d9" // New checksum
    }
}