Заметка
Доступ к этой странице требует авторизации. Вы можете попробовать войти в систему или изменить каталог.
Доступ к этой странице требует авторизации. Вы можете попробовать сменить директорию.
Хотя у компилятора нет отдельного препроцессора, директивы, описанные в этом разделе, обрабатываются так, как если бы он был. Они используются в условной компиляции. В отличие от директив 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# для этой функции.
Директивы #: , используемые в приложениях на основе файлов, включают:
#:sdk:Первая инстанция задает значение для узла
<Project Sdk="value" />. Последующие экземпляры указывают<Sdk Name="value" Version="version" />узел. Версия может быть опущена (например, если указана в global.json или включена в пакет SDK для .NET). Например:#:sdk Microsoft.NET.Sdk.Web #:sdk Aspire.AppHost.Sdk@9.4.1Два предыдущих препроцессора препроцессора препроцессоров препроцессоры препроцессоров препроцессоров преобъединяются в следующее:
<Project Sdk="Microsoft.NET.Sdk.Web" /> <Sdk Name="Aspire.AppHost.Sdk" Version="9.4.1" />#:property:Экземпляры
#:propertyпревратятся в элементы свойств в объекте<PropertyGroup>. Маркер формыName=valueдолжен следовать маркеруproperty. В приведённых ниже примерах допустимы следующие токеныproperty:#:property TargetFramework=net11.0 #:property LangVersion=previewПредыдущие два свойства превратятся в:
<TargetFramework>net11.0</TargetFramework> <LangVersion>preview</LangVersion>#:package:Экземпляры
#:packageпереводятся в элементыPackageReferenceдля включения пакетов NuGet с указанной версией в ваш файл. Например:#:package System.CommandLine@2.0.0-*Предыдущий маркер препроцессора преобразуется в:
<PackageReference Include="System.CommandLine" Version="2.0.0-*">#:project:Экземпляры
#:projectпревратятся вProjectReferenceэлементы, чтобы включить проект с указанным путем к проекту. Например:#:project ../Path/To.ExampleПредыдущий маркер препроцессора преобразуется в:
<ProjectReference Include="../Path/To.Example/To.Example.csproj" />
Инструменты могут добавлять новые токены в соответствии с #: соглашением.
Контекст, допускающий значение 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вступила в силу. В этом примере в качестве столбца 1 будет отображаться десятый столбец. Объявление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-arguments — аргументы, относящиеся к прагме.
предупреждение #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
}
}