Умовне складання та рамкові цілі


124

Є кілька мінорних місць, де код мого проекту міг би бути кардинально вдосконалений, якби цільова основа була новою версією. Я хотів би мати можливість краще використовувати умовну компіляцію в C #, щоб переключити їх у міру необхідності.

Щось на зразок:

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

Будь-який із цих символів приходить безкоштовно? Чи потрібно мені вводити ці символи як частину конфігурації проекту? Це здається досить простим, оскільки я буду знати, на які рамки орієнтовано MSBuild.

/p:DefineConstants="NET40"

Як люди вирішують цю ситуацію? Ви створюєте різні конфігурації? Ви переходите в константи через командний рядок?



Якщо ви хочете просте заздалегідь випечене рішення у VS, будь ласка, проголосуйте за цей голос користувача, visualstudio.uservoice.com/forums/121579-visual-studio/… .
JohnC

1
Ознайомтесь і з цим посиланням. Досить пояснювальна. blogs.msmvps.com/punitganshani/2015/06/21/…
Марко Алвеш

групи проектів, відновлення нугетів та групи нульових посилань, приємне рішення: shazwazza.com/post/…
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, схоже, не втрачає користувацької групи власності.

Зауважте, що це, мабуть, не дає вам нічого іншого для цільових параметрів профілю клієнта, але це не є проблемою для мене.


Джеремі, уау дякую, що це ідеально, оскільки я вже будую окремо рішення для збирання.
Грег Фінцер

+1. Хто б міг подумати, що буде так важко знайти "$ (DefineConstants.Contains ('..." ?? Дякую
CAD блокується

Нарешті я знову знайшов свій шлях до цієї сторінки, тому що мені було потрібно оновити, як я вбудував ці магічні константи в свою збірку. Я сьогодні переглядаю той самий проект, щоб підрозділити бібліотеку, і мені потрібні символи, щоб перейти зі мною до деяких підрозділів. Я просто подивився вище, і помітив, що ваша відповідь уже належним чином підтверджена в оригінальному файлі .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>

Я думаю, що такий підхід дуже читабельний і зрозумілий.


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>

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

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

3
параметри за замовчуванням і названі параметри не є функцією .NET Framework 4, а особливістю компілятора .NET 4. Їх можна використовувати також у проектах, орієнтованих на .NET 2 або .NET 3, доки вони складені у Visual Studio 2010. Це просто синтаксичний цукор. З іншого боку, динаміка є особливістю .NET Framework 4, і ви не можете використовувати її в проектах, орієнтованих на рамки до цього.
Танасіс Іоаннідіс

2

@Azarien, вашу відповідь можна поєднувати з Jeremy's, щоб тримати її в одному місці, а не налагодження | Release etc.

Для мене найкраще поєднувати обидва варіанти, тобто включати умови в код за допомогою #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>

0

Якщо ви використовуєте систему збирання .NET Core, ви можете використовувати її попередньо визначені символи (які фактично вже відповідають вашому прикладу і не потребують змін у вашому .csproj!):

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

Перелік заздалегідь заданих символів задокументовано у програмі « Розробка бібліотек за допомогою інструментів Cross Platform» та #if (C # Reference) :

.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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.