Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
Хотя компилятор не имеет отдельного препроцессора, он обрабатывает директивы, описанные в этом разделе, как если бы существовал один. Используйте эти директивы, чтобы помочь в условной компиляции. В отличие от директив 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
}
}