Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Хотя компилятор не имеет отдельного препроцессора, он обрабатывает директивы, описанные в этом разделе, как если бы существовал один. Используйте эти директивы, чтобы помочь в условной компиляции. В отличие от директив C и C++ вы не можете использовать их для создания макросов. Директива препроцессора должна быть единственной инструкцией в строке.
Справочные документы на языке C#, выпущенные последней версией языка C#. Она также содержит начальную документацию по функциям в общедоступных предварительных версиях для предстоящего языкового выпуска.
Документация определяет любую функцию, впервые представленную в последних трех версиях языка или в текущих общедоступных предварительных версиях.
Подсказка
Чтобы узнать, когда функция впервые появилась в C#, ознакомьтесь со статьей по журналу версий языка C#.
Приложения на основе файлов
Приложения на основе файлов — это программы, которые вы компилируете и запускаете с помощью dotnet run Program.cs (или любого *.cs файла). Компилятор C# игнорирует эти директивы препроцессора, но система сборки анализирует их для получения выходных данных. Эти директивы вызывают предупреждения при обнаружении в проектной компиляции.
Компилятор C# игнорирует любую директиву препроцессора, которая начинается с #: или #!.
Директива #! препроцессора позволяет оболочкам Unix напрямую выполнять файл C# с помощью dotnet run. Например:
#!/usr/bin/env dotnet run
Console.WriteLine("Hello");
Приведенный выше фрагмент кода сообщает оболочке Unix выполнить файл с помощью dotnet run. Команда /usr/bin/env находит исполняемый dotnet файл в path, что делает этот подход переносимым в разных дистрибутивах Unix и macOS. Строка #! должна быть первой строкой в файле, а следующие маркеры — программа для запуска. Необходимо включить разрешение execute (x) на файл C# для этой функции.
Директивы#:, используемые в приложениях на основе файлов, описаны в справочнике по файлам.
Другие средства могут добавлять новые маркеры после #: соглашения.
Контекст, допускающий значение NULL
Директива препроцессора #nullable задает заметки и флаги предупреждения в контексте, допускающего значение NULL,. Эта директива определяет, действуют ли заметки, допускающие значение NULL, и могут ли быть заданы предупреждения о допустимости значений NULL. Каждый из флагов отключен или включен.
Вы можете указать оба контекста на уровне проекта (за пределами исходного кода C#), добавив Nullable элемент в PropertyGroup элемент. Директива #nullable управляет флагами заметки и предупреждениями и имеет приоритет над параметрами уровня проекта. Директива задает флаг, которым она управляет, пока другая директива не переопределит его или до конца файла источника.
Ниже приведены результаты использования директив:
-
#nullable disable. Задает контекст, допускающий значение NULL, отключен. -
#nullable enable. Устанавливает допускающий значение NULL контекст включенным. -
#nullable restore: восстанавливает контекст, допускающий значение NULL, в параметры проекта. -
#nullable disable annotations. Устанавливает флаг аннотаций в контексте, допускающем значение NULL, в состояние отключено. -
#nullable enable annotations. Задает флаг заметок в контексте, допускаемом значение NULL, включено. -
#nullable restore annotations: восстанавливает флаг заметок в контексте, допускаемом значение NULL, в параметры проекта. -
#nullable disable warnings: задает флаг предупреждения в контексте, допускающего значение NULL, отключенным. -
#nullable enable warnings: Задает флаг предупреждения в контексте, допускающем значение NULL, включено. -
#nullable restore warnings: восстанавливает флаг предупреждения в nullable контексте в настройки проекта.
Условная компиляция
Используйте четыре директивы препроцессора для управления условной компиляцией:
-
#if: запускает условную компиляцию. Компилятор компилирует код только в том случае, если определен указанный символ. -
#elif: закрывает предыдущую условную компиляцию и открывает новую на основе того, определен ли указанный символ. -
#else: закрывает предыдущую условную компиляцию и открывает новую, если указанный символ не определен. -
#endif: закрывает предыдущую условную компиляцию.
Система сборки также учитывает символы препроцессора, представляющие целевые платформы в проектах в стиле SDK. Они полезны при создании приложений, предназначенных для нескольких версий .NET.
| Требуемые версии .NET Framework | Символы | Дополнительные символы (доступно в пакетах SDK для .NET 5 и более поздних версий) |
Символы платформы (доступны только при указании TFM для конкретной ОС) |
|---|---|---|---|
| .NET Framework |
NETFRAMEWORK
NET481, NET48NET472NET471NET47NET462NET461NET46NET452NET451NET45NET40NET35NET20 |
NET48_OR_GREATER, NET472_OR_GREATER, NET471_OR_GREATERNET47_OR_GREATERNET462_OR_GREATERNET461_OR_GREATERNET46_OR_GREATERNET452_OR_GREATERNET451_OR_GREATERNET45_OR_GREATERNET40_OR_GREATERNET35_OR_GREATERNET20_OR_GREATER |
|
| .NET Standard |
NETSTANDARD, NETSTANDARD2_1, NETSTANDARD2_0NETSTANDARD1_6NETSTANDARD1_5NETSTANDARD1_4NETSTANDARD1_3NETSTANDARD1_2NETSTANDARD1_1NETSTANDARD1_0 |
NETSTANDARD2_1_OR_GREATER, NETSTANDARD2_0_OR_GREATERNETSTANDARD1_6_OR_GREATERNETSTANDARD1_5_OR_GREATERNETSTANDARD1_4_OR_GREATERNETSTANDARD1_3_OR_GREATERNETSTANDARD1_2_OR_GREATERNETSTANDARD1_1_OR_GREATERNETSTANDARD1_0_OR_GREATER |
|
| .NET 5+ (и .NET Core) |
NET
NET10_0, NET9_0NET8_0NET7_0NET6_0NET5_0NETCOREAPPNETCOREAPP3_1NETCOREAPP3_0NETCOREAPP2_2NETCOREAPP2_1NETCOREAPP2_0NETCOREAPP1_1NETCOREAPP1_0 |
NET10_0_OR_GREATER, NET9_0_OR_GREATER, NET8_0_OR_GREATERNET7_0_OR_GREATERNET6_0_OR_GREATERNET5_0_OR_GREATERNETCOREAPP3_1_OR_GREATERNETCOREAPP3_0_OR_GREATERNETCOREAPP2_2_OR_GREATERNETCOREAPP2_1_OR_GREATERNETCOREAPP2_0_OR_GREATERNETCOREAPP1_1_OR_GREATERNETCOREAPP1_0_OR_GREATER |
ANDROID, BROWSER, IOSMACCATALYSTMACOSTVOSWINDOWS[OS][version] (например IOS15_1),[OS][version]_OR_GREATER (например IOS15_1_OR_GREATER) |
Примечание.
- Символы без привязки к версии определены независимо от версии, которую вы хотите использовать в качестве целевой.
- Символы для определенных версий определены только для тех версий, которые вы хотите использовать в качестве целевых.
- Символы
<framework>_OR_GREATERопределены для версии, которую вы хотите использовать в качестве целевой, и всех более ранних версий. Например, если вы выбрали .NET Framework 2.0, определяются следующие символы:NET20,NET20_OR_GREATER,NET11_OR_GREATERиNET10_OR_GREATER. -
NETSTANDARD<x>_<y>_OR_GREATERСимволы определяются только для целевых объектов .NET Standard, а не для целевых объектов, реализующих .NET Standard, таких как .NET Core и платформа .NET Framework. - Они отличаются от моникеров целевой платформы (TFM), используемых
TargetFrameworkMSBuild и NuGet.
Примечание.
Для традиционных проектов, в которых не используется пакет SDK, необходимо вручную настроить символы условной компиляции для различных целевых платформ в Visual Studio с помощью страниц свойств проекта.
Другие предопределенные символы включают константы DEBUG и TRACE. Используется #define для переопределения значений, заданных для проекта. Символ DEBUG , например, автоматически устанавливается в зависимости от свойств конфигурации сборки (режим отладки или выпуска).
Компилятор C# компилирует код между #if директивой и #endif директивой, только если определен указанный символ или не определен, если ! оператор не используется. В отличие от C и C++, нельзя назначить числовое значение символу. Оператор #if в C# является логическим и только проверяет, определён ли символ. Например, следующий код компилируется при DEBUG определении:
#if DEBUG
Console.WriteLine("Debug version");
#endif
Следующий код компилируется, если MYTESTне определен:
#if !MYTEST
Console.WriteLine("MYTEST is not defined");
#endif
Используйте операторы == (равенство) и != (неравенство) для проверки значений booltrue или false. Значение true означает, что символ определен. Инструкция #if DEBUG имеет то же значение, что и #if (DEBUG == true). Используйте операторы (и) (или) и (не) для оценки того,|| определены ли несколько символов.!&& Можно также группировать символы и операторы при помощи скобок.
В следующем примере показана сложная директива, которая позволяет коду использовать новые функции .NET при сохранении обратной совместимости. Например, представьте, что в коде используется пакет NuGet, но пакет поддерживает только .NET 6 и выше, а также .NET Standard 2.0 и выше:
#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, а также #elseдирективы , #elif#endif#defineи #undef директивы, позволяют включать или исключать код на основе существования одного или нескольких символов. Условная компиляция может быть полезной при компиляции кода для отладочной сборки или для определенной конфигурации.
Директива #elif позволяет создать составную условную директиву. Компилятор вычисляет #elif выражение, если ни предыдущие #if , ни какие-либо предыдущие, необязательные #elif , директивные выражения не оцениваются true. Если после вычисления выражения #elif возвращается значение true, компилятор вычисляет весь код между директивой #elif и следующей условной директивой. Например:
#define VC7
//...
#if DEBUG
Console.WriteLine("Debug build");
#elif VC7
Console.WriteLine("Visual Studio 7");
#endif
Директива #else позволяет создать составную условную директиву. Если ни одно из выражений в предыдущих #if или необязательных #elif директивах не trueоценивается, компилятор вычисляет весь код между #else следующими #endifдирективами.
#endif должен быть следующей директивой препроцессора после #else.
#endif указывает на конец условной директивы, начало которой было задано с помощью директивы #if.
В следующем примере показано, как определить MYTEST символ в файле, а затем проверить значения MYTEST и DEBUG символы. Выходные данные этого примера зависят от того, создан ли проект в режиме конфигурации отладки или выпуска .
#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
}
}
В следующем примере показано, как протестировать различные целевые платформы, чтобы по возможности использовать более новые API:
public class MyClass
{
static void Main()
{
#if NET40
WebClient _client = new WebClient();
#else
HttpClient _client = new HttpClient();
#endif
}
//...
}
Определение символов
Используйте следующие две директивы препроцессора для определения или отмены символов для условной компиляции:
-
#define: определение символа. -
#undef: подчеркивание символа.
Используется #define для определения символа. При использовании символа в качестве выражения, переданного директиве #if, выражение оценивается как true, как показано в следующем примере:
#define VERBOSE
#if VERBOSE
Console.WriteLine("Verbose output version");
#endif
Примечание.
В C# примитивные константы должны быть определены с помощью ключевого const слова. Объявление const создает static элемент, который нельзя изменить во время выполнения. Директива #define не может использоваться для объявления константных значений, как правило, в C и C++. При наличии нескольких констант имеет смысл создать для них отдельный класс "Constants".
Используйте символы для указания условий компиляции. Проверка символа с помощью одного #if или.#elif Для условной компиляции также можно использовать ConditionalAttribute. Вы можете определить символ, но не можете присвоить символу значение. Директива #define должна находиться в файле перед использованием любых инструкций, которые также не являются директивами препроцессора. Вы также можете определить символ с помощью параметра компилятора DefineConstants . Отмена определения символа с помощью #undef.
Определение областей
Определите регионы кода, которые можно свернуть в структуре с помощью следующих двух директив препроцессора:
-
#region: начало области. -
#endregion: конец области.
Директива #region позволяет указать блок кода, который можно разворачивать и сворачивать с помощью функции структурирования в редакторе кода. В больших файлах кода удобно сворачивать или скрывать одну область или несколько, чтобы не отвлекаться от той части файла, над которой в настоящее время идет работа. В следующем примере показано, как определить область:
#region MyClass definition
public class MyClass
{
static void Main()
{
}
}
#endregion
В конце блока #region должна присутствовать директива #endregion. Блок #region не может накладываться на блок #if. Однако блок #region можно вложить в блок #if, а блок #if — в блок #region.
Сведения об ошибках и предупреждениях
Для создания определяемых пользователем ошибок и предупреждений компилятора и сведений о строке управления можно использовать следующие директивы:
-
#error: создание ошибки компилятора с указанным сообщением. -
#warning: создание предупреждения компилятора с конкретным сообщением. -
#line: изменение номера строки, выводимого с сообщениями компилятора.
Используется #error для создания определяемой пользователем ошибки CS1029 из определенного расположения в коде. Например:
#error Deprecated code in this method.
Примечание.
Компилятор обрабатывает #error version особым образом и сообщает об ошибке компилятора CS8304 с сообщением, содержащим используемые версии компилятора и языка.
Используется #warning для создания предупреждения компилятора на уровне CS1030 из определенного расположения в коде. Например:
#warning Deprecated code in this method.
Используется #line для изменения нумеровки строки компилятора и (необязательно) выходных данных имени файла для ошибок и предупреждений.
В следующем примере показано, как включить в отчет два предупреждения, связанные с номерами строк. Директива #line 200 заставляет номер следующей строки иметь значение 200 (хотя значение по умолчанию равно #6), а до следующей #line директивы имя файла сообщается как "Специальный". Директива #line default возвращает нумерацию строк в нумерацию по умолчанию, которая подсчитывает строки, изменяемые предыдущей директивой.
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;
}
}
В результате компиляции формируются следующие результаты:
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
Директива #line может использоваться на автоматизированном промежуточном этапе процесса построения. Например, если удалить строки из исходного файла исходного кода, но компилятор по-прежнему хочет создать выходные данные на основе исходного нумератора строк в файле, можно удалить строки, а затем имитировать исходное нумерирование строк с помощью.#line
Директива #line hidden скрывает последовательные строки от отладчика, таким образом, когда разработчик выполняет шаги по коду, все строки между #line hidden директивой и следующей #line директивой (при условии, что она не является другой #line hidden директивой), переходятся. Этот параметр также можно использовать для того, чтобы дать ASP.NET возможность различать определяемый пользователем и создаваемый компьютером код. Хотя ASP.NET является основным потребителем этой функции, скорее всего, более исходные генераторы используют его.
Директива #line hidden не влияет на имена файлов и номера строк в отчетах об ошибках. То есть, если компилятор находит ошибку в скрытом блоке, компилятор сообщает текущее имя файла и номер строки ошибки.
Директива #line filename задает имя файла, которое будет отображаться в выходных данных компилятора. По умолчанию используется фактическое имя файла с исходным кодом. Имя файла должно быть в двойных кавычках ("") и должно соответствовать номеру строки.
Вы можете использовать новую форму директивы #line:
#line (1, 1) - (5, 60) 10 "partial-class.cs"
/*34567*/int b = 0;
Компоненты этой формы:
-
(1, 1): начальная строка и столбец первого символа в строке, следующей за директивой. В этом примере следующая строка сообщается как строка 1, столбец 1. -
(5, 60): конечная строка и столбец для помеченной области. -
10: смещение столбца, чтобы директива#lineвступила в силу. В этом примере 10-й столбец сообщается как столбец один. Объявлениеint b = 0;начинается с этого столбца. Это поле необязательно. Если этот параметр опущен, директива вступает в силу в первом столбце. -
"partial-class.cs": имя выходного файла.
В предыдущем примере создается следующее предупреждение:
partial-class.cs(1,5,1,6): warning CS0219: The variable 'b' is assigned but its value is never used
После повторного сопоставления переменная b находится в первой строке в символе шесть из файла partial-class.cs.
Предметно-ориентированные языки (DSL) обычно используют этот формат, чтобы обеспечить более эффективное сопоставление исходного файла с созданными выходными данными C#. Наиболее распространенное использование этой расширенной директивы #line заключается в ремаппинге предупреждений или ошибок, которые появляются в созданном файле, в исходный код. Например, рассмотрим эту страницу razor:
@page "/"
Time: @DateTime.NowAndThen
Свойство DateTime.Now вводится неправильно DateTime.NowAndThenкак . Созданный C# для этого фрагмента razor выглядит следующим образом:page.g.cs
_builder.Add("Time: ");
#line (2, 6) - (2, 27) 15 "page.razor"
_builder.Add(DateTime.NowAndThen);
Выходные данные компилятора для предыдущего фрагмента кода:
page.razor(2, 2, 2, 27)error CS0117: 'DateTime' does not contain a definition for 'NowAndThen'
Строка 2, столбец 6 в page.razor — это место, где начинается текст @DateTime.NowAndThen, отмеченный как (2, 6) в директиве. Этот диапазон @DateTime.NowAndThen заканчивается в строке 2, столбце 27, отмеченном (2, 27) в директиве. Текст для DateTime.NowAndThen начинается в столбце 15 таблицы page.g.cs, отмеченном как 15 в директиве. Компилятор сообщает об ошибке в его расположении в page.razor. Разработчик может перейти непосредственно к ошибке в исходном коде, а не к созданному источнику.
Дополнительные примеры этого формата см. в разделе примеров в спецификации функции.
Директивы pragma
Компилятор использует #pragma для получения специальных инструкций по компиляции файла, в котором он отображается. Компилятор должен поддерживать используемые прагмы. Другими словами, директиву #pragma невозможно использовать для создания настраиваемых инструкций предварительной обработки.
-
#pragma warning: включение или отключение предупреждений. -
#pragma checksum: создание контрольной суммы.
#pragma pragma-name pragma-arguments
pragma-name — имя распознанного pragma.
pragma-arguments — это аргументы, относящиеся к pragma.
предупреждение #pragma
#pragma warning может включать или отключать определенные предупреждения.
#pragma warning disable format и #pragma warning enable format управляют форматированием блоков кода в Visual Studio.
#pragma warning disable warning-list
#pragma warning restore warning-list
warning-list — это разделенный запятыми список чисел предупреждений, например 414, CS3021. Префикс CS является необязательным. Если вы не указываете номера предупреждений, disable отключает все предупреждения и restore включает все предупреждения.
Примечание.
Чтобы найти номера предупреждений в Visual Studio, выполните сборку проекта, а затем поиск номеров предупреждений в окне Вывод.
Параметр disable вступает в силу, начиная со следующей строки исходного файла. Предупреждение восстанавливается в строке после restore. Если в файле нет restore , предупреждения восстанавливают состояние по умолчанию в первой строке всех последующих файлов в той же компиляции.
// 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()
{
}
}
Другая форма warning pragma отключает или восстанавливает команды форматирования Visual Studio в блоках кода:
#pragma warning disable format
#pragma warning restore format
Команды форматирования Visual Studio не изменяют текст в блоках кода, где disable format действует. Команды форматирования, такие как CTRL+K, CTRL+D, не изменяют эти области кода. Эта pragma позволяет точно управлять визуальным представлением кода.
#pragma контрольная сумма
Создает контрольные суммы для исходных файлов для отладки ASP.NET страниц.
#pragma checksum "filename" "{guid}" "checksum bytes"
Директива используется "filename" в качестве имени файла для отслеживания изменений или обновлений в "{guid}" качестве глобально уникального идентификатора (GUID) для хэш-алгоритма, а "checksum_bytes" также в качестве строки шестнадцатеричных цифр, представляющих байты контрольной суммы. Необходимо указать четное число шестнадцатеричных цифр. Нечетное число цифр приведет к выводу предупреждения во время компиляции, и директива будет пропущена.
Отладчик Visual Studio использует контрольную сумму, чтобы подтвердить нахождение правильного источника. Компилятор вычисляет контрольную сумму для исходного файла, а затем передает результат в файл базы данных (PDB) программы. Отладчик использует PDB для сравнения с контрольной суммой, вычисляемой для исходного файла.
Это решение не работает для проектов ASP.NET, так как рассчитанная контрольная сумма относится к созданному исходному файлу, а не файлу ASPX. Чтобы решить эту проблему, #pragma checksum предоставляет поддержку контрольных сумм для страниц ASP.NET.
При создании проекта ASP.NET в Visual C# созданный исходный файл содержит контрольную сумму для ASPX-файла, из которого создается источник. Затем компилятор записывает эти данные в PDB-файл.
Если компилятор не обнаруживает директиву #pragma checksum в файле, он вычисляет контрольную сумму и записывает значение в PDB-файл.
class TestClass
{
static int Main()
{
#pragma checksum "file.cs" "{406EA660-64CF-4C82-B6F0-42D48172A799}" "ab007f1d23d9" // New checksum
}
}