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 #if
a #endif
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 #else
diretivas , #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 , NET481 , , 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 , NET9_0 , , 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 ) |
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_GREATER
eNET10_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
TargetFramework
MSBuild 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.
Pode utilizar um novo formulário da diretiva #line
:
#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çãoint 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 warning
: Ativar ou desativar avisos. -
#pragma checksum
: Gere uma soma de verificação.
#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
}
}