Kompilacja warunkowa i cele platformy

124

Jest kilka pomniejszych miejsc, w których kod mojego projektu mógłby zostać drastycznie ulepszony, gdyby docelowa platforma była nowszą wersją. Chciałbym móc lepiej wykorzystać kompilację warunkową w C #, aby przełączyć je w razie potrzeby.

Coś jak:

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

Czy któryś z tych symboli jest bezpłatny? Czy muszę wstrzykiwać te symbole w ramach konfiguracji projektu? Wydaje się to łatwe, ponieważ wiem, który framework jest przeznaczony dla MSBuild.

/p:DefineConstants="NET40"

Jak ludzie radzą sobie z tą sytuacją? Czy tworzysz różne konfiguracje? Czy przekazujesz stałe za pomocą wiersza poleceń?

mckamey
źródło
Jeśli potrzebujesz prostego, gotowego rozwiązania w VS, zagłosuj na ten głos użytkownika, visualstudio.uservoice.com/forums/121579-visual-studio/… .
JohnC
1
Spójrz również na ten link. Dość wyjaśniające. blogs.msmvps.com/punitganshani/2015/06/21/…
Marco Alves
grupy projektów, nuget restore i nuget ref groups, fajne rozwiązanie: shazwazza.com/post/…
OzBob

Odpowiedzi:

119

Jednym z najlepszych sposobów osiągnięcia tego jest utworzenie różnych konfiguracji kompilacji w projekcie:

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

I w jednej z domyślnych konfiguracji:

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

Który ustawiłby wartość domyślną, gdyby nie została zdefiniowana nigdzie indziej. W powyższym przypadku OutputPath da ci oddzielny zestaw za każdym razem, gdy budujesz każdą wersję.

Następnie utwórz cel AfterBuild, aby skompilować różne wersje:

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

Ten przykład przekompiluje cały projekt ze zmienną Framework ustawioną na NET20 po pierwszej kompilacji (kompilując oba i zakładając, że pierwsza kompilacja była domyślną NET35 z góry). Każda kompilacja będzie miała poprawnie ustawione wartości warunkowe.

W ten sposób możesz nawet wykluczyć niektóre pliki w pliku projektu, jeśli nie chcesz #ifdef plików:

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

a nawet referencje

<Reference Include="Some.Assembly" Condition="" '$(Framework)' == 'NET20' " >
  <HintPath>..\Lib\$(Framework)\Some.Assembly.dll</HintPath>
</Reference>
Todd
źródło
Idealny. Miałem wystarczające doświadczenie w hakowaniu formatu msbuild, aby wiedzieć, że można to zrobić, ale nie miałem wystarczająco dużo czasu, aby dowiedzieć się wszystkich szczegółów. Dziękuję Ci bardzo!
mckamey
Jeśli dodasz odniesienie do tej odpowiedzi w moim pokrewnym pytaniu ( stackoverflow.com/questions/2923181 ), oznaczę Cię tam jako rozwiązanie. To faktycznie rozwiązuje oba z nich w tym samym czasie.
mckamey
7
Dzięki za odpowiedź, ale teraz VS2010 zawiera już nowy tag o nazwie „TargetFrameworkVersion”, teraz dla każdej grupy właściwości z warunkiem zmienia się tylko TargetFrameworkVersion. Czy nadal potrzebujemy tego wszystkiego, aby działało?
Akash Kava
Ta odpowiedź nie dotyczy tylko zdefiniowania stałych dla frameworka, ale także tworzenia dla wielu frameworków
katbyte
4
Ten post zadziałał dla mnie, ale nie jestem dobry w MSBuild i zajęło trochę czasu, zanim to zrozumiałem. Zrobiłem projekt, który działa jako przykład. dev6.blob.core.windows.net/blog-images/DualTargetFrameworks.zip
TheDev6
44

Alternatywą, która działa dla mnie do tej pory, jest dodanie do pliku projektu:

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

Przyjmuje wartość właściwości TargetFrameworkVersion, która podobnie jak „v3.5” zastępuje „v” i „”. aby uzyskać „NET35” (używając nowej funkcji Property Functions ). Następnie usuwa wszelkie istniejące wartości „NETxx” i dodaje je na końcu właściwości DefinedConstants. Być może uda się to usprawnić, ale nie mam czasu na zabawę.

Patrząc na kartę Kompilacja we właściwościach projektu w VS, zobaczysz wynikową wartość w sekcji symboli kompilacji warunkowej. Zmiana docelowej wersji platformy na karcie Aplikacja powoduje następnie automatyczną zmianę symbolu. Następnie można używać #if NETxxdyrektyw preprocesora w zwykły sposób. Zmiana projektu w VS nie powoduje utraty niestandardowej PropertyGroup.

Zauważ, że to nie wydaje się dawać niczego innego dla opcji docelowych Profilu Klienta, ale nie jest to dla mnie problem.

Jeremy Cook
źródło
Jeremy, wow dzięki, to jest idealne, ponieważ już buduję osobno w moim rozwiązaniu do kompilacji.
Greg Finzer
+1. Kto by pomyślał, że tak trudno będzie znaleźć „$ (DefineConstants.Contains ('...” ?? Dzięki
facet z CAD
W końcu ponownie trafiłem na tę stronę, ponieważ potrzebowałem przypomnienia sobie, jak wprowadziłem te magiczne stałe do mojej kompilacji. Wracam dziś do tego samego projektu, aby podzielić bibliotekę i potrzebuję symboli, które zostaną ze mną podzielone na niektóre podziały. Właśnie spojrzałem powyżej i zauważyłem, że twoja odpowiedź jest już należycie potwierdzona w oryginalnym pliku .CSPROJ.
David A. Grey
15

Miałem problemy z tymi rozwiązaniami, być może dlatego, że moje początkowe stałe zostały wstępnie zbudowane przez te właściwości.

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

Visual Studio 2010 również zwrócił błąd z powodu średników, twierdząc, że są to niedozwolone znaki. Komunikat o błędzie dał mi wskazówkę, ponieważ mogłem zobaczyć wstępnie zbudowane stałe oddzielone przecinkami, po których ostatecznie występuje mój „nielegalny” średnik. Po ponownym sformatowaniu i zmasowaniu udało mi się znaleźć rozwiązanie, które działa dla mnie.

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

Chciałbym opublikować zrzut ekranu okna dialogowego Zaawansowane ustawienia kompilatora (otwieranego przez kliknięcie przycisku „Zaawansowane opcje kompilacji ...” na karcie Kompiluj projektu). Ale jako nowy użytkownik brakuje mi przedstawiciela, aby to zrobić. Gdybyś mógł zobaczyć zrzut ekranu, zobaczyłbyś niestandardowe stałe wypełniane automatycznie przez grupę właściwości, a następnie powiedziałbyś: „Muszę coś dla mnie przynieść”.


EDYCJA: Mam tego przedstawiciela zaskakująco szybko ... Dzięki! Oto ten zrzut ekranu:

Zaawansowane ustawienia kompilatora

Nathaniel Roark
źródło
4

Zacznij od wyczyszczenia stałych:

<PropertyGroup>
  <DefineConstants/>
</PropertyGroup>

Następnie zbuduj swoje debugowanie, śledzenie i inne stałe, takie jak:

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

Na koniec zbuduj stałe ramowe:

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

Myślę, że to podejście jest bardzo czytelne i zrozumiałe.

zDougie
źródło
3

W pliku .csproj, po istniejącej <DefineConstants>DEBUG;TRACE</DefineConstants>linii, dodaj to:

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

Zrób to dla konfiguracji kompilacji debugowania i wydania. Następnie użyj w swoim kodzie:

#if NET_40_OR_GREATER
   // can use dynamic, default and named parameters
#endif
Azarien
źródło
3
Parametry domyślne i nazwane nie są funkcją platformy .NET Framework 4, ale funkcją kompilatora .NET 4. Mogą być używane także w projektach na .NET 2 lub .NET 3, o ile są skompilowane w Visual Studio 2010. To tylko cukier składniowy. Z drugiej strony, dynamiczny jest funkcją .NET Framework 4 i nie można go używać w projektach przeznaczonych dla platform wcześniejszych.
Thanasis Ioannidis,
2

@Azarien, twoja odpowiedź może być połączona z odpowiedzią Jeremy'ego, aby zachować ją w jednym miejscu, zamiast Debugowanie | Wydanie itp.

Dla mnie połączenie obu odmian działa najlepiej, tj. Włączanie warunków w kodzie za pomocą #if NETXX, a także tworzenie za jednym razem różnych wersji frameworka.

Mam te w moim pliku .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>

aw tarczach:

  <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
źródło
0

Jeśli korzystasz z systemu kompilacji .NET Core, możesz użyć jego wstępnie zdefiniowanych symboli (które w rzeczywistości pasują do Twojego przykładu i nie wymagają żadnych zmian .csproj!):

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

Lista predefiniowanych symboli jest udokumentowana w Developing Libraries with Cross Platform Tools and #if (C # Reference) :

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

NET standardowe: NETSTANDARD , NETSTANDARD1_0, NETSTANDARD1_1, NETSTANDARD1_2, NETSTANDARD1_3, NETSTANDARD1_4, NETSTANDARD1_5, NETSTANDARD1_6, NETSTANDARD2_0,NETSTANDARD2_1

NET Rdzeń: NETCOREAPP , NETCOREAPP1_0, NETCOREAPP1_1, NETCOREAPP2_0, NETCOREAPP2_1, NETCOREAPP2_2,NETCOREAPP3_0

Kevinoid
źródło