Условные цели компиляции и структуры

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

Что-то вроде:

#if NET40
using FooXX = Foo40;
#elif NET35
using FooXX = Foo35;
#else NET20
using FooXX = Foo20;
#endif

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

/p:DefineConstants="NET40"

Как люди справляются с этой ситуацией? Вы создаете разные конфигурации? Вы передаете константы через командную строку?

Ответ 1

Один из лучших способов добиться этого - создать в своем проекте различные конфигурации сборки:

<PropertyGroup Condition="  '$(Framework)' == 'NET20' ">
  <DefineConstants>NET20</DefineConstants>
  <OutputPath>bin\$(Configuration)\$(Framework)</OutputPath>
</PropertyGroup>


<PropertyGroup Condition="  '$(Framework)' == 'NET35' ">
  <DefineConstants>NET35</DefineConstants>
  <OutputPath>bin\$(Configuration)\$(Framework)</OutputPath>
</PropertyGroup>

И в одной из конфигураций по умолчанию:

<Framework Condition=" '$(Framework)' == '' ">NET35</Framework>

Что бы установить значение по умолчанию, если оно не было определено нигде. В приведенном выше случае OutputPath даст вам отдельную сборку каждый раз, когда вы создаете каждую версию.

Затем создайте цель AfterBuild для компиляции различных версий:

<Target Name="AfterBuild">
  <MSBuild Condition=" '$(Framework)' != 'NET20'"
    Projects="$(MSBuildProjectFile)"
    Properties="Framework=NET20"
    RunEachTargetSeparately="true"  />
</Target>

В этом примере будет перекомпилирован весь проект с переменной Framework, установленной в NET20 после первой сборки (скомпилирование обоих и предположение, что первая сборка была по умолчанию NET35 сверху). Каждый компилятор будет правильно задавать значения условного определения.

Таким образом, вы даже можете исключить определенные файлы в файл проекта, если хотите, чтобы не было #ifdef файлов:

<Compile Include="SomeNet20SpecificClass.cs" Condition=" '$(Framework)' == 'NET20' " />

или даже ссылки

<Reference Include="Some.Assembly" Condition="" '$(Framework)' == 'NET20' " >
  <HintPath>..\Lib\$(Framework)\Some.Assembly.dll</HintPath>
</Reference>

Ответ 2

Альтернативой, которая работает для меня до сих пор, является добавление в файл проекта следующего содержания:

 <PropertyGroup>
    <DefineConstants Condition=" !$(DefineConstants.Contains(';NET')) ">$(DefineConstants);$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", ""))</DefineConstants>
    <DefineConstants Condition=" $(DefineConstants.Contains(';NET')) ">$(DefineConstants.Remove($(DefineConstants.LastIndexOf(";NET"))));$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", ""))</DefineConstants>
  </PropertyGroup>

Это берет значение свойства TargetFrameworkVersion, которое похоже на "v3.5", заменяет "v" и ".". получить "NET35" (используя новую функцию Функции функций). Затем он удаляет любое существующее значение "NETxx" и добавляет его в конец DefinedConstants. Возможно, это будет возможно, но у меня нет времени на скрипку.

Посмотрев на вкладке "Сборка" свойств проекта в VS, вы увидите результирующее значение в разделе условных компиляций. Изменение версии целевой структуры на вкладке "Приложение" автоматически изменяет символ. Затем вы можете использовать директивы препроцессора #if NETxx обычным способом. Изменение проекта в VS не похоже на пользовательскую PropertyGroup.

Обратите внимание, что это не дает вам ничего другого для целевых параметров профиля клиента, но это не проблема для меня.

Ответ 3

У меня были проблемы с этими решениями, возможно из-за того, что мои исходные константы были предварительно построены этими свойствами.

<DefineConstants />
<DefineDebug>true</DefineDebug>
<DefineTrace>true</DefineTrace>
<DebugSymbols>true</DebugSymbols>

Visual Studio 2010 также выдала ошибку из-за полуколоней, утверждая, что они являются незаконными символами. Сообщение об ошибке дало мне подсказку, поскольку я мог видеть предварительно построенные константы, разделенные запятыми, в конце концов последовали за моей "незаконной" точкой с запятой. После некоторого переформатирования и массажа я смог придумать решение, которое работает для меня.

<PropertyGroup>
  <!-- Adding a custom constant will auto-magically append a comma and space to the pre-built constants.    -->
  <!-- Move the comma delimiter to the end of each constant and remove the trailing comma when we're done.  -->
  <DefineConstants Condition=" !$(DefineConstants.Contains(', NET')) ">$(DefineConstants)$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", "")), </DefineConstants>
  <DefineConstants Condition=" $(DefineConstants.Contains(', NET')) ">$(DefineConstants.Remove($(DefineConstants.LastIndexOf(", NET"))))$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", "")), </DefineConstants>
  <DefineConstants Condition=" $(TargetFrameworkVersion.Replace('v', '')) >= 2.0 ">$(DefineConstants)NET_20_OR_GREATER, </DefineConstants>
  <DefineConstants Condition=" $(TargetFrameworkVersion.Replace('v', '')) >= 3.5 ">$(DefineConstants)NET_35_OR_GREATER</DefineConstants>
  <DefineConstants Condition=" $(DefineConstants.EndsWith(', ')) ">$(DefineConstants.Remove($(DefineConstants.LastIndexOf(", "))))</DefineConstants>
</PropertyGroup>

Я бы опубликовал снимок экрана диалогового окна "Дополнительные параметры компилятора" (открывается нажатием кнопки "Расширенные параметры компиляции..." на вкладке "Компиляция" вашего проекта). Но, как новый пользователь, мне не хватает репутации для этого. Если бы вы могли увидеть скриншот, вы увидите, что пользовательские константы будут автоматически заполнены группой свойств, а затем вы будете говорить: "Мне нужно получить часть из них".


РЕДАКТИРОВАТЬ: Получил этот репорт на удивление быстро. Спасибо, ребята! Вот этот снимок экрана:

Advanced Compiler Settings

Ответ 4

Начните с очистки констант:

<PropertyGroup>
  <DefineConstants/>
</PropertyGroup>

Затем создайте свои отладочные, следовые и другие константы, например:

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
    <DebugSymbols>true</DebugSymbols>
  <DebugType>full</DebugType>
  <Optimize>false</Optimize>
  <DefineConstants>TRACE;DEBUG;$(DefineConstants)</DefineConstants>
</PropertyGroup>

Наконец, создайте константы фреймворка:

<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v2.0' ">
  <DefineConstants>NET10;NET20;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v3.0' ">
  <DefineConstants>NET10;NET20;NET30;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v3.5' ">
  <DefineConstants>NET10;NET20;NET30;NET35;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">
  <DefineConstants>NET10;NET20;NET30;NET35;NET40;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v4.5' ">
  <DefineConstants>NET10;NET20;NET30;NET35;NET40;NET45;$(DefineConstants)</DefineConstants>
</PropertyGroup>

Я думаю, что этот подход очень читабельен и понятен.

Ответ 5

В файле .csproj, после существующей строки <DefineConstants>DEBUG;TRACE</DefineConstants>, добавьте следующее:

<DefineConstants Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' &gt;= '4.0' ">NET_40_OR_GREATER</DefineConstants>
<DefineConstants Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' == '4.0' ">NET_40_EXACTLY</DefineConstants>

Сделайте это для конфигураций сборки Debug и Release. Затем используйте в своем коде:

#if NET_40_OR_GREATER
   // can use dynamic, default and named parameters
#endif

Ответ 6

@Azarien, ваш ответ может быть объединен с Джереми, чтобы держать его в одном месте, а не отлаживать | Release и т.д.

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

У меня есть это в моем файле .csproj:

  <PropertyGroup>
    <DefineConstants Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' &gt;= '4.0' ">NET_40_OR_GREATER</DefineConstants>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' == '3.5' ">
    <DefineConstants>NET35</DefineConstants>
    <OutputPath>bin\$(Configuration)\$(TargetFrameworkVersion)</OutputPath>
  </PropertyGroup>

и в целях:

  <Target Name="AfterBuild">
    <MSBuild Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' &gt;= '4.0' "
      Projects="$(MSBuildProjectFile)"
      Properties="TargetFrameworkVersion=v3.5"
      RunEachTargetSeparately="true"  />
  </Target>