Как установить условную переменную компиляции?

В C/С++ вы можете определить макросы в коде следующим образом:

#define OLD_WAY  1

Хотя я никогда этого не делал, я предполагаю, что то же самое доступно на С#. Более того, в C/С++ можно выполнить некоторую условную логику компиляции, выполнив что-то вроде этого:

#if OLD_WAY == 1
 int i = 0;
#else
 int i = 1;
#endif

ОК, так что все это круто и все такое. И снова, я предполагаю, что такая логика возможна внутри С#. Я хотел бы знать, как определить константы на уровне проекта, чтобы я мог поместить логику, которая позволит мне условно скомпилировать один блок кода, если я определяю константу в одном направлении или другой блок кода если я этого не определяю? Я предполагаю, что это сделано где-то в свойствах проекта, но как и где его определить?

Ответ 1

Компилятор С# csc.exe и сам язык С# не предоставляют никаких предопределенных констант для условной компиляции. Visual Studio добавляет только значения DEBUG и TRACE, которые можно настроить через IDE. Среда IDE также позволяет добавлять собственные произвольные символы, но, поскольку они являются по существу фиксированными (инвариантными) значениями, возможности ограничены.

Более мощные пользовательские параметры можно настроить, отредактировав файл проекта .csproj вручную. Вы можете настроить условия здесь, чтобы выборочно распространять символы условной компиляции в С# на основе огромного количества информации о среде и конфигурации, доступной в MSBuild (см. здесь и здесь, но в принципе не может быть полного списка, поскольку разрозненные компоненты произвольно предоставляют метаданные ad-hoc).

Давайте рассмотрим рабочий пример. Один из случаев, когда полезно условно компилировать, - это если вы хотите написать код, который адаптируется к любым инструментам, обнаруженным во время сборки. Таким образом, вы можете использовать новейшие языковые функции, сохраняя при этом возможность компилирования на компьютерах с более старыми инструментами, которые, как и ожидалось, отклоняли бы чужой синтаксис и/или ключевые слова. Для конкретного случая С# 7.0 в Visual Studio 2017 мы можем изменить .csproj следующим образом:

Файл .csproj (выдержка):

Enter image description here

Вы также можете идентифицировать каждый из старых компиляторов С#, постепенно снижая качество. То же самое касается обнаружения версии .a Framework (часто запрашивается в Qaru [1] [2] [3] [4]) и любые другие условия окружающей среды. Это оставлено в качестве упражнений для читателя, но в случае, если вы хотите скопировать/вставить выделенные строки сверху, вот текстовая версия. В качестве обновления снимка экрана я добавил здесь условные выражения в кавычки (хотя казалось, что все работает без них)

<DefineConstants Condition="'$(VisualStudioVersion)'=='15'">CSHARP7</DefineConstants>
<!-- ... -->
<DefineConstants>DEBUG;TRACE;$(DefineConstants)</DefineConstants>
<!-- ... -->
<DefineConstants>TRACE;$(DefineConstants)</DefineConstants>

В любом случае, теперь вы можете написать условный код С#, используя #if… #elif… #else… #endif. Продолжая пример, в приведенном ниже коде используется новый синтаксис кортежа - доступный только в С# 7 - для замены элементов массива. Кстати, версия кортежа не только более лаконична и/или элегантна; он также производит отличный код CIL:

#if CSHARP7
    (rg[i], rg[j]) = (rg[j], rg[i]);  // Swap elements: tuple syntax
#else
    var t = rg[i];                    // Swap elements: clunky
    rg[i] = rg[j];
    rg[j] = t;
#endif

Обратите внимание, что Visual Studio IDE правильно обрабатывает ваши ручные настройки .csproj во всех отношениях. Учитывая .csproj, который я показал ранее, редактор кода IDE правильно распознает и оценивает условную компиляцию для целей IntelliSense, refactoring, "неактивных" неактивных блоков кода и т.д.

Я также упомянул, что MSBuild обладает огромным запасом информации, из которых $(VisualStudioVersion) был только одним примером. К сожалению, нелегко узнать, какие значения доступны и какие они могут иметь во время сборки. Хитрость заключается в том, чтобы временно поместить проект C++ в решение Visual Studio (если у вас его еще нет) вместе с проектом С#. Если вы щелкните правой кнопкой мыши свойства проекта для этого .vcxproj, а затем посмотрите (например) "Дополнительные каталоги включения" на странице C/C++, в правом нижнем углу появится раскрывающийся список, когда вы щелкните, чтобы редактировать :

Enter image description here

Вы получите диалоговое окно с кнопкой "Макросы", которую вы можете щелкнуть, чтобы получить список всех доступных переменных MSBuild плюс их ожидаемые значения в соответствии с платформой и конфигурацией, которые в настоящее время выбраны в IDE. Не пропустите хорошо известные поля метаданных элемента (с префиксом %) внизу списка.

Enter image description here

Вы можете получить представление о том, сколько вещей здесь, по размеру большого пальца полосы прокрутки на этом скриншоте. (Они перечислены в алфавитном порядке; я просто прокрутил эту часть раздела "P", потому что там было минимальное количество личной информации.) Однако важно отметить, что как (доступные) переменные, так и их значения изменяются с течением времени в течение ход сборки, поэтому вы можете найти в этом списке элементы, которые не были доступны вашему .csproj во время его обработки.


Другой способ узнать, какие значения свойств доступны во время и во время процесса сборки, - установить для MSBuild "подробность вывода" значение "Подробно", а затем перестроить.

Enter image description here

После завершения сборки проверьте верхнюю часть журнала сборки в Visual Studio Окно вывода, и вы увидите список доступных имен свойств вместе с их начальными значениями.

Ответ 2

В С# вы можете сделать #define, но вы не можете использовать значения для них, как вы можете в C++. Каждое определение может иметь два состояния: определено или неопределено

В свойствах проекта в Build вы можете задать определения, которые должны быть определены. Все, что вы укажете здесь, будет определено во всех ваших файлах проекта.

Так, например, я могу определить два символа условной компиляции в этом поле как:

MY_DEFINE1, MY_DEFINE2

Тогда в моем коде я могу делать такие вещи:

#if MY_DEFINE1
    // Do something conditionally
#endif

#if MY_DEFINE2
    // Do something else conditionally
#endif

В качестве альтернативы вы можете сделать свои определения для каждого файла, но в отличие от C++ они должны быть в верхней части вашего файла.

В верхней части вашего файла вы можете использовать:

#define MY_DEFINE2

Или в верхней части файла вы можете использовать:

#undef MY_DEFINE2

Это последнее, что вы должны сделать, если установите символ условной компиляции и хотите, чтобы он был во всех файлах, кроме, возможно, одного.

Ответ 3

Откройте свойства вашего проекта и посмотрите на страницу Build. Есть поле с названием Условные символы компиляции:

Enter image description here