Diretivas de pré-processador do C#
Embora o compilador não tenha um pré-processador separado, as diretivas descritas nesta seção são processadas como se houvesse um. Você os usa para ajudar na compilação condicional. Ao contrário das diretivas de C e C++, não é possível usar essas diretivas para criar macros. Uma diretiva de pré-processador deve ser a única instrução em uma linha.
Contexto que permite valor nulo
A diretiva de pré-processador #nullable
define o contexto de anotação anulável e o contexto de aviso anulável. Essa diretiva controla se as anotações anuláveis têm efeito e se os avisos de nulidade são dados. Cada contexto está desabilitado ou habilitado.
Ambos os contextos podem ser especificados no nível do projeto (fora do código-fonte C#) adicionando o elemento Nullable
ao elemento PropertyGroup
. A diretiva #nullable
controla os contextos de anotação e aviso e tem precedência sobre as configurações no nível do projeto. Uma diretiva define os contextos 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 desabilitado.#nullable enable
: define os contextos de aviso e anotação anuláveis como habilitado.#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 anulada como desabilitado.#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 desabilitado.#nullable enable warnings
: define o contexto de aviso anulável como habilitado.#nullable restore warnings
: restaura o contexto de aviso anulável nas 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, em que o código é compilado somente se o símbolo especificado for definido.#elif
: fecha a compilação condicional anterior e abre uma nova compilação condicional com base no caso do símbolo especificado ser 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 #if
e a diretiva #endif
somente se o símbolo especificado está definido ou não definido quando o operador !
não é usado. Ao contrário do C e do C++, não é possível atribuir um valor numérico a um símbolo. A instrução #if
em C# é booliana e testa apenas quando o símbolo foi definido ou não. Por exemplo, o seguinte código é compilado quando DEBUG
é definido:
#if DEBUG
Console.WriteLine("Debug version");
#endif
O seguinte código é compilado quando MYTEST
não é 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 foi definido. A instruçã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. Também é possível agrupar os símbolos e operadores com parênteses.
A seguir, uma diretiva complexa que permite que seu código aproveite os recursos mais recentes do .NET e, ao mesmo tempo, mantenha a compatibilidade com versões anteriores. Por exemplo, imagine que você esteja usando um pacote NuGet em seu código, mas o pacote dá suporte apenas para o .NET 6 e superior, bem como com o .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 diretivas #else
, #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 começa com #if
deve ser terminada explicitamente com uma diretiva #endif
. A diretiva #define
permite definir um símbolo. Ao usar o símbolo como a expressão passada para a diretiva #if
, a expressão será avaliada como true
. Você também pode definir um símbolo com a opção do compilador DefineConstants. Você pode anular a definição 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 de 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.
O #elif
permite criar uma diretiva condicional composta. A expressão #elif
será avaliada se nem as expressões de diretiva anterior #if
nem outras anteriores, opcionais #elif
são avaliadas como true
. Se uma expressão #elif
for avaliada como true
, o compilador avaliará todo o código entre #elif
e a próxima diretiva condicional. Por exemplo:
#define VC7
//...
#if DEBUG
Console.WriteLine("Debug build");
#elif VC7
Console.WriteLine("Visual Studio 7");
#endif
#else
permite que você crie uma diretiva condicional composta, para que, caso nenhuma das expressões nas diretivas anteriores #if
ou (opcional) #elif
seja avaliada como true
, o compilador avalie todo o código entre #else
e a próxima #endif
. #endif
(#endif) deve ser a próxima diretiva de pré-processador após #else
.
#endif
especifica o final de uma diretiva condicional, que começou com a diretiva #if
.
O sistema de compilação também está ciente dos símbolos de pré-processamento predefinidos que representam várias estruturas de destino em projetos no estilo SDK. Eles são úteis ao criar aplicativos que podem ter como destino mais de uma versão do .NET.
Frameworks de destino | Símbolos | Símbolos adicionais (disponível em SDKs do .NET 5+) |
Símbolos de plataforma (disponíveis somente quando você especifica um TFM específico do sistema operacional) |
---|---|---|---|
.NET Framework | NETFRAMEWORK , NET48 , NET472 , NET471 , NET47 , NET462 , NET461 , NET46 , NET452 , NET451 , NET45 , NET40 , NET35 , NET20 |
NET48_OR_GREATER , NET472_OR_GREATER , NET471_OR_GREATER , NET47_OR_GREATER , NET462_OR_GREATER , NET461_OR_GREATER , NET46_OR_GREATER , NET452_OR_GREATER , NET451_OR_GREATER , NET45_OR_GREATER , NET40_OR_GREATER , NET35_OR_GREATER , NET20_OR_GREATER |
|
.NET Standard | NETSTANDARD , NETSTANDARD2_1 , NETSTANDARD2_0 , NETSTANDARD1_6 , NETSTANDARD1_5 , NETSTANDARD1_4 , 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_4_OR_GREATER , NETSTANDARD1_3_OR_GREATER , NETSTANDARD1_2_OR_GREATER , NETSTANDARD1_1_OR_GREATER , NETSTANDARD1_0_OR_GREATER |
|
.NET 5+ (e .NET Core) | NET , NET8_0 , NET7_0 , NET6_0 , NET5_0 , NETCOREAPP , NETCOREAPP3_1 , NETCOREAPP3_0 , NETCOREAPP2_2 , NETCOREAPP2_1 , NETCOREAPP2_0 , NETCOREAPP1_1 , NETCOREAPP1_0 |
NET8_0_OR_GREATER , NET7_0_OR_GREATER , NET6_0_OR_GREATER , NET5_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 , MACCATALYST , MACOS , TVOS , WINDOWS ,[OS][version] (por exemplo, IOS15_1 ),[OS][version]_OR_GREATER (por exemplo, IOS15_1_OR_GREATER ) |
Observação
- Os símbolos sem versão são definidos independentemente da versão para a qual você está direcionando.
- Os símbolos específicos à versão são definidos apenas para a versão que você está direcionando.
- Os símbolos
<framework>_OR_GREATER
são definidos para a versão que você está direcionando e todas as versões anteriores. Por exemplo, se você estiver direcionando .NET Framework 2.0, os seguintes símbolos serão definidos:NET20
,NET20_OR_GREATER
,NET11_OR_GREATER
eNET10_OR_GREATER
. - Os símbolos
NETSTANDARD<x>_<y>_OR_GREATER
são definidos apenas para destinos .NET Standard, e não para destinos que implementam o .NET Standard, como o .NET Core e o .NET Framework. - Eles são diferentes dos TFMs (Moniker da Estrutura de Destino) usados pela propriedade MSBuild
TargetFramework
e pelo NuGet.
Observação
Para projetos tradicionais, não 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 DEBUG
e TRACE
. Para substituir os valores definidos no projeto, use a diretiva #define
. Por exemplo, o símbolo DEBUG é definido automaticamente, de acordo com as propriedades de configuração do build (Modo de Depuração ou Modo de Versão).
O exemplo a seguir mostra como definir um símbolo MYTEST
em um arquivo e testar os valores dos símbolos MYTEST
e DEBUG
. A saída deste exemplo depende da sua escolha ao compilar o projeto: se você optou pelo 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 várias 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
}
//...
}
Definindo símbolos
Use as duas diretivas de pré-processador seguintes para definir ou anular a definição de símbolos para compilação condicional:
#define
: define um símbolo.#undef
: anula a definição de um símbolo.
Use #define
para definir um símbolo. Quando você usa o símbolo como a expressão passada para a diretiva #if
, a expressão será avaliada como true
, conforme mostra o seguinte exemplo:
#define VERBOSE
#if VERBOSE
Console.WriteLine("Verbose output version");
#endif
Observação
No C#, as constantes primitivas devem ser definidas usando a palavra-chave const
. Uma declaração const
cria um membro static
que não pode ser modificado em runtime. A diretiva #define
não pode ser usada para declarar valores de constantes como normalmente é feito no C e C++. Se você tiver várias dessas constantes, considere criar uma classe "Constantes" separada para guardá-las.
Os símbolos podem ser usados para especificar condições para compilação. É possível testar o símbolo com #if
ou #elif
. Você também pode usar o ConditionalAttribute para executar uma compilação condicional. É possível definir um símbolo, mas não é possível atribuir um valor a um símbolo. A diretiva #define
deve ser exibida no arquivo antes de usar as instruções que também não são diretivas de pré-processador. Você também pode definir um símbolo com a opção do compilador DefineConstants. Você pode anular a definição um símbolo com #undef
.
Definindo 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 seguintes:
#region
: inicia uma região.#endregion
: encerra uma região.
#region
permite que você especifique um bloco de código que pode ser expandido ou recolhido ao usar o recurso de estrutura de tópicos do editor de código. 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 que 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 bloco #region
deve ser encerrado com a diretiva #endregion
. Um #region
bloco não pode se sobrepor a um bloco #if
. No entanto, um bloco #region
pode ser aninhado em um bloco #if
e um bloco #if
pode ser aninhado em um bloco #region
.
Informações sobre erros e avisos
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 do compilador com uma mensagem especificada.#warning
: gere um aviso do compilador com uma mensagem específica.#line
: altere o número de linha impresso com mensagens do compilador.
#error
permite gerar um erro definido pelo usuário CS1029 de um local específico em seu código. Por exemplo:
#error Deprecated code in this method.
Observação
O compilador trata #error version
de maneira especial e relata um erro do compilador, CS8304, com uma mensagem que contém o compilador usado e as versões de idioma.
#warning
permite gerar um aviso do compilador CS1030 de nível um de um local específico no código. Por exemplo:
#warning Deprecated code in this method.
O #line
permite modificar o número de linha do compilador e (opcionalmente) a saída do nome de arquivo para erros e avisos.
O exemplo a seguir mostra como relatar dois avisos associados aos números de linha. A diretiva #line 200
força o próximo número de linha a ser 200 (embora o padrão seja #6) e, até a próxima diretiva #line
, o nome de arquivo será relatado como "Special". A diretiva #line default
retorna a numeração de linhas à 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 saída a seguir:
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 diretiva #line
pode ser usada em uma etapa intermediária e automatizada no processo de build. Por exemplo, se linhas fossem removidas do arquivo de código-fonte original, mas você ainda deseja que o compilador gere a saída com base na numeração de linha original no arquivo, seria possível remover as linhas e, em seguida, simular a numeração de linha original com #line
.
A diretiva #line hidden
oculta as linhas sucessivas do depurador, de modo que, quando o desenvolvedor percorrer o código, quaisquer linhas entre um #line hidden
e a próxima diretiva #line
(supondo que não seja outra diretiva #line hidden
) serão puladas. Essa opção também pode ser usada para permitir que o ASP.NET diferencie entre o código gerado pelo computador e definido pelo usuário. Embora o ASP.NET seja o principal consumidor desse recurso, é provável que mais geradores de origem façam uso dele.
A diretiva #line hidden
não afeta os nomes de arquivo ou números de linha nos relatórios de erro. Ou seja, se o compilador encontrar um erro em um bloco oculto, o compilador relatará o nome do arquivo atual e o número de linha do erro.
A diretiva #line filename
especifica o nome de arquivo que você deseja que seja exibido na saída do compilador. Por padrão, é usado o nome real do arquivo de código-fonte. O nome de arquivo deve estar entre aspas duplas ("") e deve ser precedido por um número de linha.
Do C# 10 em diante, você pode usar uma nova forma da diretiva #line
:
#line (1, 1) - (5, 60) 10 "partial-class.cs"
/*34567*/int b = 0;
Os componentes dessa noda forma são:
(1, 1)
: a linha de início 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 de extremidade e a coluna da região marcada.10
: o deslocamento da coluna para que a diretiva#line
entre em vigor. Neste exemplo, a 10ª coluna seria relatada como coluna um. É aí que começa a declaraçãoint b = 0;
. Esse campo é opcional. Se omitida, a diretiva vai entrar em vigor na primeira coluna."partial-class.cs"
: 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
Depois de remapear, a variável b
está na primeira linha, no caractere seis do arquivo partial-class.cs
.
As DSLs (linguagens específicas do domínio) normalmente usam esse formato para fornecer um mapeamento melhor do arquivo de origem para a saída gerada em C#. O uso mais comum dessa diretiva #line
estendida é mapear novamente avisos ou erros que aparecem em um arquivo gerado para a origem original. Por exemplo, considere esta página razor:
@page "/"
Time: @DateTime.NowAndThen
A propriedade DateTime.Now
foi digitada incorretamente como DateTime.NowAndThen
. O C# gerado para o snippet razor é semelhante ao seguinte, 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 snippet anterior é:
page.razor(2, 2, 2, 27)error CS0117: 'DateTime' does not contain a definition for 'NowAndThen'
Linha 2, coluna 6 em page.razor
é onde o texto @DateTime.NowAndThen
começa. Isso é notado por (2, 6)
na diretiva . Esse intervalo de @DateTime.NowAndThen
termina na linha 2, coluna 27. Isso é notado pelo (2, 27)
na diretiva . O texto para DateTime.NowAndThen
começa na coluna 15 de page.g.cs
. Isso é notado pelo 15
na diretiva . Juntando todos os argumentos, o compilador relata o erro em sua localização em page.razor
. O desenvolvedor pode navegar diretamente até o erro no código-fonte, não na origem gerada.
Para ver mais exemplos desse formato, confira a especificação de recurso na seção sobre exemplos.
Pragmas
O #pragma
fornece ao compilador instruções especiais para a compilação do arquivo no qual ele é exibido. O compilador deve dar suporte às instruções. Em outras palavras, não é possível usar #pragma
para criar instruções personalizadas de pré-processamento.
#pragma warning
: habilite ou desabilite avisos.#pragma checksum
: gere uma soma de verificação.
#pragma pragma-name pragma-arguments
Em que pragma-name
é o nome de um pragma reconhecido e pragma-arguments
são os argumentos específicos do pragma.
#pragma warning
O #pragma warning
pode habilitar ou desabilitar determinados avisos.
#pragma warning disable warning-list
#pragma warning restore warning-list
Em que warning-list
é uma lista de números de aviso separada por vírgulas. O prefixo "CS" é opcional. Quando não houver números de aviso especificados, o disable
desabilita todos os avisos e o restore
habilita todos os avisos.
Observação
Para localizar números de aviso no Visual Studio, compile o projeto e, em seguida, procure os números de aviso na janela de Saída.
disable
entra em vigor desde a próxima linha do arquivo de origem. O aviso é restaurado na linha que segue restore
. Se não houver nenhum restore
no arquivo, os avisos serão restaurados para o estado padrão na primeira linha de qualquer arquivo posterior 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 checksum
Gera somas de verificação para os arquivos de origem para ajudar na depuração de páginas do ASP.NET.
#pragma checksum "filename" "{guid}" "checksum bytes"
Em que "filename"
é o nome do arquivo que requer monitoramento para alterações ou atualizações, "{guid}"
é o GUID (Globally Unique Identifier) para o algoritmo hash e "checksum_bytes"
é a cadeia de caracteres de dígitos hexadecimal 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 sempre a localização da fonte correta. O compilador calcula a soma de verificação para um arquivo de origem e, em seguida, emite a saída no arquivo PDB (banco de dados do programa). 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 do ASP.NET, porque é a soma de verificação calculada para o arquivo de origem gerado e não para o arquivo .aspx. Para resolver esse problema, a #pragma checksum
fornece suporte à soma de verificação para páginas do ASP.NET.
Quando você cria um projeto do ASP.NET em Visual C#, o arquivo de origem gerado contém uma soma de verificação para o arquivo .aspx, do qual a fonte é gerada. Então, o compilador grava essas informações no arquivo PDB.
Se o compilador não encontrar uma diretiva #pragma checksum
no arquivo, ele calcula a soma de verificação e grava o valor no arquivo PDB.
class TestClass
{
static int Main()
{
#pragma checksum "file.cs" "{406EA660-64CF-4C82-B6F0-42D48172A799}" "ab007f1d23d9" // New checksum
}
}