Директива #define (C/C++)
#define создает макрос, который является связью идентификатора или параметризованного идентификатора со строкой токена. После определения макроса компилятор может подставить строку токена для каждого обнаруженного идентификатора в исходном файле.
Синтаксис
#define маркер-строкаидентификатора
#define идентификатор (идентификатор opt, . ,identifier opt)token-string opt
Замечания
Директива #define приводит компилятору заменить строку маркера для каждого вхождения идентификатора в исходном файле. Идентификатор заменяется только в том случае, если он формирует маркер. То есть идентификатор не заменяется, если он отображается в комментарии, в строке или в составе более длинного идентификатора. Дополнительные сведения см. в разделе «Токены».
Аргумент строки токена состоит из ряда маркеров, таких как ключевое слово, константы или полные операторы. Один или несколько символов пробелов должны отделять строку маркера от идентификатора. Эти пробелы не считаются частью замененного текста, как и все остальные пробелы, следующие за последним токеном текста.
Без #define строки маркера удаляется вхождения идентификатора из исходного файла. Идентификатор остается определенным и может быть проверен с помощью #if defined директив и #ifdef инструкций.
Вторая форма синтаксиса определяет макрос, подобный функции, с параметрами. Эта форма допускает использование необязательного списка параметров, которые должны находиться в скобках. После определения макроса каждое последующее вхождение идентификатора (opt , . идентификатор opt) заменяется версией аргумента строки токена, который имеет фактические аргументы, заменяемые формальными параметрами.
Имена формальных параметров отображаются в строке токена, чтобы пометить расположения, в которых заменяются фактические значения. Каждое имя параметра может отображаться несколько раз в строке маркера, и имена могут отображаться в любом порядке. Число аргументов в вызове должно соответствовать числу параметров в определении макроса. Надлежащее использование скобок обеспечит правильную обработку сложных фактических аргументов.
Формальные параметры в списке разделяются запятыми. Все имена в списке должны быть уникальными, и список должен быть заключен в скобки. Пробелы не могут разделять идентификатор и открываемую скобку. Используйте объединение строк — поместите обратную косую косую черту ( \ ) непосредственно перед новым символом — для длинных директив в нескольких исходных строках. Область формального имени параметра распространяется на новую строку, которая заканчивается строкой токена.
Если макрос определен во второй форме синтаксиса, последующие текстовые экземпляры, за которыми находится список аргументов, указывают на вызов макроса. Фактические аргументы, следующие за экземпляром идентификатора в исходном файле, соответствуют соответствующим формальным параметрам в определении макроса. Каждый формальный параметр в строке токена, который не предшествует строковой (), charizing ( #@ # ), или оператор вставки маркеров ( ## ) или не следует ## оператору, заменяется соответствующим фактическим аргументом. Перед заменой директивой формального параметра все макросы в фактическом аргументе разворачиваются. (Операторы описаны в разделе Операторы препроцессора.)
В следующих примерах макросов с аргументами показана вторая форма синтаксиса #define :
// Macro to define cursor lines #define CURSOR(top, bottom) (((top)
Аргументы с побочными эффектами иногда приводят к тому, что макросы дают непредвиденные результаты. Заданный формальный параметр может отображаться несколько раз в строке токена. Если этот формальный параметр заменяется выражением с побочными эффектами, выражение с такими эффектами может вычисляться несколько раз. (См. примеры в разделе Оператор вставки токенов (#).)
Директива #undef приводит к тому, что определение препроцессора идентификатора забывается. Дополнительные сведения см . в директиве #undef.
Если имя определяемого макроса происходит в строке маркера (даже в результате другого расширения макроса), он не расширяется.
Второй #define для макроса с тем же именем создает предупреждение, если только вторая последовательность маркеров не идентична первой.
Блок, относящийся только к системам Майкрософт
Если новое определение синтаксически совпадает с исходным, Microsoft C и C++ позволяют переопределить макрос. Другими словами, два определения могут иметь разные имена параметров. Это поведение отличается от ANSI C, которое требует, чтобы два определения были лексически идентичны.
Например, следующие два макроса идентичны, за исключением имен параметров. ANSI C не разрешает такое переопределение, но Microsoft C/C++ компилирует его без ошибок.
#define multiply( f1, f2 ) ( f1 * f2 ) #define multiply( a1, a2 ) ( a1 * a2 )
С другой стороны, следующие два макроса неидентичны и приводят к выдаче предупреждения в Microsoft C и C++.
#define multiply( f1, f2 ) ( f1 * f2 ) #define multiply( a1, a2 ) ( b1 * b2 )
Завершение блока, относящегося только к системам Майкрософт
В этом примере показана директива #define :
#define WIDTH 80 #define LENGTH ( WIDTH + 10 )
Первый оператор определяет идентификатор WIDTH как целочисленную константу 80, а затем LENGTH задается в виде WIDTH и целочисленной константы 10. Каждое вхождение LENGTH заменяется на ( WIDTH + 10 ). В свою очередь, каждое вхождение WIDTH + 10 заменяется выражением ( 80 + 10 ). Скобки вокруг WIDTH + 10 имеют важное значение, поскольку управляют интерпретацией в операторах, например в следующем:
var = LENGTH * 20;
После этапа предварительной обработки этот оператор принимает следующий вид:
var = ( 80 + 10 ) * 20;
что равно 1800. Без скобок результат будет следующим:
var = 80 + 10 * 20;
Блок, относящийся только к системам Майкрософт
Определение макросов и констант с параметром компилятора /D имеет тот же эффект, что и при использовании директивы предварительной обработки #define в начале файла. С помощью параметра /D можно определить до 30 макросов.
Завершение блока, относящегося только к системам Майкрософт
#define
Директива #define определяет идентификатор и последовательность символов, которой будет замещаться данный идентификатор при его обнаружении в тексте программы. Идентификатор также называется именем макроса, а процесс замещения называется подстановкой макроса. Стандартный вид директивы следующий:
#define имя_макроса последовательность_символов
Обратим внимание, что в данном операторе отсутствует точка с запятой. Между идентификатором и последовательностью символов может быть любое число пробелов. Макрос завершается только переходом на новую строку.
Например, если необходимо использовать TRUE для значения 1, a FALSE для 0 то можно объявить следующие два макроса:
#define TRUE 1
#define FALSE 0
В результате, если компилятор обнаружит в тексте программы TRUE или FALSE, то он заменит их на 1 и 0 соответственно. Например, следующая строка выводит на экран «0 1 2»:
printf ("%d %d %d", FALSE, TRUE, TRUE + 1);
В случае, если макрос определен, он может использоваться для определения других макросов. Например, следующий код сопоставляет с именами ONE, TWO и THREE их численные значения:
#define ONE 1
#define TWO ONE + ONE
#def ine THREE ONE + TWO
В результате макроподстановки идентификаторы замещаются указанными строками. Если необходимо определить стандартное сообщение об ошибке, то можно написать что-то вроде следующего:
#define E_MS "Standart error on input.\n"
/*. */
printf(E_MS);
Если компилятор обнаруживает идентификатор E_MS, то он замещает его строкой «Standart error on input.» На самом деле компилятор увидит оператор в виде
printf("Standart error on input.\n");
Если идентификатор находится в строке, то подстановка не происходит. Например:
#define XYZ this is a test
/*. */
printf("XYZ");
выведет не «this is a test», a «XYZ».
Если строка не вмещается в одной строке, то ее можно продолжить на следующей строке, поместив в конце строки обратный слэш, как показано в следующем примере:
#define LONG_STRING "This is a very long" \
string that is used as an example."
Программисты, пишущие на С, часто используют заглавные буквы для определения идентификаторов. Данное соглашение помогает любому человеку, читающему программу, бросив на нее один взгляд, узнать, что он имеет дело с макросом. Также вce #define лучше помещать в начале файла или вообще в отдельный заголовочный файл.
Очень часто макросы используют для определения «магических чисел», используемых в программе. Например, программа может определять массив и иметь несколько процедур для работы с ним. Вместо того, чтобы жестко кодировать размер массива, лучше определить макрос, соответствующий размеру массива, и использовать его в тех местах, где необходимо использование размера. Таким образом, если необходимо изменить размер массива, единственное, что требуется сделать, — это изменить оператор #define и перекомпилировать программу. Везде, где использовался данный макрос, произойдут автоматические изменения. Рассмотрим пример:
#define MAX_SIZE 100
/*. */
float balance[MAX_SIZE];
/*. */
float temp[MAX_SIZE];
Для изменения размеров обоих массивов просто изменим определение MAX_SIZE.
Директива #define имеет еще одну возможность: макрос может иметь аргументы. Каждый раз при встрече такого макроса аргументы макроса будут замещаться реальными аргументами программы. Такой тип макроса называется макрос типа функция. Например:
#include
#define MIN(a,b) ((a) <(b)) ? (a) : (b)
int main(void)
int x, y;
x = 10; у = 20;
printf("The minimum is: %d", MIN(x, y) );
return 0;
>
При компиляции программы вместо MIN(a, b) подставляется указанное выражение, причем вместо фиктивных параметров а и b подставляются реальные х и у. Таким образом, в результате подстановки функция printf() примет следующий вид:
Надо быть осторожным при определении макросов, получающих аргументы, или можно получить несколько неожиданные результаты. Например, рассмотрим следующую короткую программу, использующую макрос для определения четности значения:
/* программа выдает неправильный результат */
#include
#define EVEN(a) a%2==0 ? 1 : 0
int main(void)
if (EVEN(9+1) ) printf("is even");
else printf ("is odd");
return 0;
>
Из-за способа подстановки данная программа работает неправильно. В результате компиляции программы EVEN(9 + 1) расширится до
9 + 1% 2 == 0 ? 1 : 0
Как известно, оператор взятия по модулю имеет более высокий приоритет, чем оператор сложения. Это означает, что сначала выполнится взятие по модулю с числом 1, а затем результат прибавится к 9, что, естественно, не может быть равно 0. Для устранения данной проблемы следует заключить а в макросе EVEN в круглые скобки, как показано в следующей правильной версии программы:
#include
#define EVEN(a) (a)%2==0 ? 1 : 0
int main(void)
if(EVEN(9 + 1) ) printf("is even");
else printf("is odd");
return 0;
>
Обратим внимание, что 9+1 вычисляется до взятия по модулю. В целом заключение параметров макроса в скобки — это достаточно хорошая идея, и она позволяет избежать множества проблем.
Использование макроподстановок вместо реальных функций имеет одно большое преимущество — существенно увеличивается скорость работы программы, поскольку нет необходимости тратить время на вызов функции и возврат из нее. Тем не менее, за данное увеличение скорости работы следует платить увеличением размера исполнимого кода программы, поскольку программа вынуждена дублировать код макроса.
#define с++ для чего служит
хотелось бы уточнить 1 это своего рода константа а 2 это функция то же вроде константы и после компиляции cpp файла в exe файл, эти константы не будут включены в исполняемом файле,то есть если декомпилировать будет следующее
int i = 315; int j = 20;
я правильно понимаю
Отслеживать
задан 1 дек 2018 в 16:48
143 10 10 бронзовых знаков
1 дек 2018 в 16:51
Директива препроцессора #define заменяет один кусок текста на другой. В этом примере в первом случае после замены действительно будет на int i = 315; однако int j = (2+2)*(3+2); во втором.
1 дек 2018 в 16:54
Вы бы хоть постарались корректно воспроизвести код. То, что вы написали - просто ошибочно (опечатки).
1 дек 2018 в 16:59
Если компилятор (после препроцессора) вычисляет константные выражения (обычно это так), то в принципе (с точностью до выбора декомпайлером имен), правильно
1 дек 2018 в 18:04
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Директива #define сама по себе осуществляет только текстовую подстановку, но не предвычисление выражений с константами. То есть, если исправить объявление PRIMERF на корректное (у вас пропущена запятая):
#define PRIMERF(V1,V2) (V1+2)*(V2+2)
, то на этапе обработки препроцессором PRIMERF(2,3) превратится в (2+2)*(3+2) .
Предвычисление константного выражения может быть выполнено на этапе компиляции, но это не гарантируется. Скажем, Visual C++ выполняет его, в том числе и при отключенной оптимизации:
#include #include #define PRIMERP 315 #define PRIMERF(V1,V2)(V1+2)*(V2+2) int main(int argc, char **argv) < int i = PRIMERP; int j = PRIMERF(2,3); printf("%d %d",i,j); ; getchar(); return 0; >/* Disassembly: _main: push ebp mov ebp,esp sub esp,8 push esi mov dword ptr [j],0CCCCCCCCh mov dword ptr [i],0CCCCCCCCh mov dword ptr [i],13Bh // int i = PRIMERP; mov dword ptr [j],14h // int j = PRIMERF(2,3); mov esi,esp mov eax,dword ptr [j] push eax mov ecx,dword ptr [i] push ecx push 0F05858h call dword ptr ds:[0F092C0h] // printf("%d %d",i,j); add esp,0Ch cmp esi,esp call _RTC_CheckEsp (0F01220h) mov esi,esp call dword ptr ds:[0F092C8h] // getchar(); cmp esi,esp call _RTC_CheckEsp (0F01220h) xor eax,eax pop esi add esp,8 cmp ebp,esp call _RTC_CheckEsp (0F01220h) mov esp,ebp pop ebp ret */
14h - это предвычисленное значение выражения (20 в шестнадцатеричном виде)
Директивы препроцессора C#
Хотя у компилятора нет отдельного препроцессора, директивы, описанные в этом разделе, обрабатываются так, как если бы он был. Они используются в условной компиляции. В отличие от директив C и C++ вы не можете использовать их для создания макросов. Директива препроцессора должна быть единственной инструкцией в строке.
Контекст, допускающий значение NULL
Директива препроцессора #nullable устанавливает контекст с заметками о допустимости значений NULL и контекст с предупреждениями о допустимости значений NULL. Эта директива определяет, действуют ли заметки, допускающие значение NULL, и могут ли быть заданы предупреждения о допустимости значений NULL. Каждый контекст либо отключен, либо включен.
Оба контекста можно указать на уровне проекта (за пределами исходного кода C#). Директива #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 : восстанавливает контекст предупреждения, допускающего значение NULL, в параметры проекта.
Условная компиляция
Для управления условной компиляцией используются четыре директивы препроцессора.
- #if : открывает условную компиляцию, где код компилируется, только если определен указанный символ.
- #elif : закрывает предыдущую условную компиляцию и открывает новую на основе того, определен ли указанный символ.
- #else : закрывает предыдущую условную компиляцию и открывает новую, если указанный символ не определен.
- #endif : закрывает предыдущую условную компиляцию.
Компилятор 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
Вы можете использовать операторы == (равенство) и != (неравенство) для проверки значений bool true или false . Значение true означает, что символ определен. Инструкция #if DEBUG имеет то же значение, что и #if (DEBUG == true) . Вы можете использовать операторы && (и), || (или) и ! (не), чтобы узнать, определено ли несколько символов. Можно также группировать символы и операторы при помощи скобок.
#if , а также #else директивы , #endif #elif #define и #undef директивы, позволяют включать или исключать код на основе существования одного или нескольких символов. Условная компиляция может быть полезной при компиляции кода для отладочной сборки или для определенной конфигурации.
Условные директивы, начинающиеся с директивы #if , должны явным образом завершаться директивой #endif . #define позволяет определить символ, чтобы выражение, в качестве которого этот символ передается в директиву #if , при вычислении давало значение true . Символ также можно определить с помощью параметра компилятора DefineConstants. Символ можно отменить с #undef помощью. Символ, создаваемый с помощью #define , будет определен в пределах того файл, в котором он определен. Символ, определенный с помощью DefineConstants или #define , не конфликтует с одноименной переменной. Соответственно, имя переменной не должно передаваться директиве препроцессора, а символ может использоваться только в директиве препроцессора.
Директива #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 .
Система сборки также учитывает символы препроцессора, представляющие целевые платформы в проектах в стиле SDK. Они полезны при создании приложений, предназначенных для нескольких версий .NET.
| Требуемые версии .NET Framework | Символы | Дополнительные символы (доступно в пакетах SDK для .NET 5 и более поздних версий) |
Символы платформы (доступны только при указании TFM для конкретной ОС) |
|---|---|---|---|
| .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+ (и .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] (например IOS15_1 ), [OS][version]_OR_GREATER (например IOS15_1_OR_GREATER ) |
- Символы без привязки к версии определены независимо от версии, которую вы хотите использовать в качестве целевой.
- Символы для определенных версий определены только для тех версий, которые вы хотите использовать в качестве целевых.
- Символы _OR_GREATER определены для версии, которую вы хотите использовать в качестве целевой, и всех более ранних версий. Например, если вы выбрали .NET Framework 2.0, определяются следующие символы: NET20 , NET20_OR_GREATER , NET11_OR_GREATER и NET10_OR_GREATER .
- NETSTANDARD__OR_GREATER Символы определяются только для целевых объектов .NET Standard, а не для целевых объектов, реализующих .NET Standard, таких как .NET Core и платформа .NET Framework.
- Они отличаются от моникеров целевой платформы (TFM), используемых свойством MSBuild TargetFramework и NuGet.
Для традиционных проектов, в которых не используется пакет SDK, необходимо вручную настроить символы условной компиляции для различных целевых платформ в Visual Studio с помощью страниц свойств проекта.
Другие предопределенные символы включают константы DEBUG и TRACE . Вы можете переопределить значения для проектов с помощью #define . Например, символ DEBUG автоматически устанавливается в зависимости от свойств конфигурации сборки (в режиме отладки или выпуска).
В следующем примере показано, как определить символ 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
Директиву #define нельзя использовать для объявления значений констант, как это обычно делается в C и 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 в отчете будет указываться имя файла Special. Директива #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 задает имя файла, которое будет отображаться в выходных данных компилятора. По умолчанию используется фактическое имя файла с исходным кодом. Имя файла должно заключаться в двойные кавычки (" "). Перед ним должен указываться номер строки.
Начиная с C# 10 можно использовать новую форму директивы #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 warning
#pragma warning может включать или отключать определенные предупреждения.
#pragma warning disable warning-list #pragma warning restore warning-list
warning-list — список номеров предупреждений с разделителем-запятой. Префикс 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() < >>
#pragma checksum
Создает контрольные суммы для исходных файлов, чтобы помочь с отладкой страниц ASP.NET.
#pragma checksum "filename" "" "checksum bytes"
"filename" — это имя файла, для которого требуется наблюдение за изменениями или обновлениями, "" — глобальный уникальный идентификатор (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" "" "ab007f1d23d9" // New checksum > >
Совместная работа с нами на GitHub
Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.