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

124

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

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

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

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

/p:DefineConstants="NET40"

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

mckamey
источник
Если вам нужно простое предварительно запеченное решение в VS, проголосуйте за этот голос пользователя: visualstudio.uservoice.com/forums/121579-visual-studio/… .
JohnC
1
Взгляните также на эту ссылку. Довольно объяснительно. blogs.msmvps.com/punitganshani/2015/06/21/…
Марко Алвес
группы проектов, восстановление nuget
OzBob,

Ответы:

119

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

<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>
Тодд
источник
Отлично. У меня было достаточно опыта взлома формата msbuild, чтобы знать, что это можно сделать, но не хватило времени, чтобы разобраться во всех деталях. Большое спасибо!
mckamey
Если вы добавите ссылку на этот ответ на мой связанный с этим вопрос ( stackoverflow.com/questions/2923181 ), я отмечу вас как решение. На самом деле это решает обе проблемы одновременно.
mckamey
7
Спасибо за ответ, но теперь VS2010 уже включает новый тег с именем «TargetFrameworkVersion», теперь для каждой группы свойств с условием изменяется только TargetFrameworkVersion, нужно ли все это для работы?
Акаш Кава
Этот ответ касается не только определения констант для фреймворка, но и построения для нескольких фреймворков
katbyte
4
Этот пост сработал для меня, но я плохо разбираюсь в MSBuild, и мне потребовалось время, чтобы понять это. Я сделал проект, который работает как пример. dev6.blob.core.windows.net/blog-images/DualTargetFrameworks.zip
TheDev6
44

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

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

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

Джереми Кук
источник
Джереми, вау, спасибо, это идеально, так как я уже создаю отдельно в своем решении для сборки.
Грег Финзер
+1. Кто бы мог подумать , что это будет так трудно найти «$ (DefineConstants.Contains (» ...»?? Спасибо
CAD Bloke
Наконец-то я снова попал на эту страницу, потому что мне нужно было напомнить, как я добавил эти магические константы в свою сборку. Сегодня я возвращаюсь к тому же проекту, чтобы разделить библиотеку, и мне нужны символы, которые можно было бы разделить со мной на некоторые из подразделов. Я только что посмотрел выше и заметил, что ваш ответ уже должным образом подтвержден в исходном файле .CSPROJ.
Дэвид А. Грей
15

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

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

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


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

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

Натаниэль Рорк
источник
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>

Считаю такой подход очень читабельным и понятным.

zDougie
источник
3

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

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

#if NET_40_OR_GREATER
   // can use dynamic, default and named parameters
#endif
Azarien
источник
3
Параметры по умолчанию и именованные параметры - это не функция .NET framework 4, а функция компилятора .NET 4. Их также можно использовать в проектах, ориентированных на .NET 2 или .NET 3, если они скомпилированы в Visual Studio 2010. Это просто синтаксический сахар. С другой стороны, динамический - это функция .NET framework 4, и вы не можете использовать ее в проектах, ориентированных на платформы до этого.
Thanasis Ioannidis
2

@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>
ghanashyaml
источник
0

Если вы используете систему сборки .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

Kevinoid
источник