Условная компиляция и цели фреймворка

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

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

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

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

/p:DefineConstants="NET40"

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

6 ответов

Решение

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

<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>

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

 <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. Может быть возможно упростить это, но у меня нет времени, чтобы возиться.

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

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

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

<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>

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


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

Расширенные настройки компилятора

Если вы используете систему сборки.NET Core, вы можете использовать ее предопределенные символы (которые фактически уже соответствуют вашему примеру и не требуют каких-либо изменений в вашем .csproj!):

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

Список предопределенных символов задокументирован в разделе Разработка библиотек с помощью кроссплатформенных инструментов и #if (Справочник по C#):

.NET Framework: NETFRAMEWORK, NET20, NET35, NET40, NET45, NET451, NET452, NET46, NET461, NET462, NET47, NET471, NET472, NET48

Стандарт.NET: NETSTANDARD, NETSTANDARD1_0, NETSTANDARD1_1, NETSTANDARD1_2, NETSTANDARD1_3, NETSTANDARD1_4, NETSTANDARD1_5, NETSTANDARD1_6, NETSTANDARD2_0, NETSTANDARD2_1

.NET Core: NETCOREAPP, NETCOREAPP1_0, NETCOREAPP1_1, NETCOREAPP2_0, NETCOREAPP2_1, NETCOREAPP2_2, NETCOREAPP3_0

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

<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>

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

В файле.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

@Azarien, ваш ответ может быть объединен с ответом Джереми, чтобы держать его в одном месте, а не в Debug|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>
Другие вопросы по тегам