Bedingte Kompilierung und Rahmenziele

124

Es gibt einige kleinere Stellen, an denen der Code für mein Projekt möglicherweise drastisch verbessert werden kann, wenn das Zielframework eine neuere Version wäre. Ich möchte die bedingte Kompilierung in C # besser nutzen können, um diese nach Bedarf zu wechseln.

Etwas wie:

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

Ist eines dieser Symbole kostenlos? Muss ich diese Symbole im Rahmen der Projektkonfiguration einfügen? Dies scheint einfach zu sein, da ich weiß, auf welches Framework MSBuild abzielt.

/p:DefineConstants="NET40"

Wie gehen die Menschen mit dieser Situation um? Erstellen Sie verschiedene Konfigurationen? Übergeben Sie die Konstanten über die Befehlszeile?

mckamey
quelle
Wenn Sie eine einfache vorgefertigte Lösung in VS wünschen, stimmen Sie diese Benutzerstimme ab: visualstudio.uservoice.com/forums/121579-visual-studio/… .
JohnC
1
Schauen Sie sich auch diesen Link an. Ziemlich erklärend. blogs.msmvps.com/punitganshani/2015/06/21/…
Marco Alves
Projektgruppen, Nuget Restore und Nuget Ref Gruppen, nette Lösung: shazwazza.com/post/…
OzBob

Antworten:

119

Eine der besten Möglichkeiten, dies zu erreichen, besteht darin, verschiedene Build-Konfigurationen in Ihrem Projekt zu erstellen:

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

Und in einer Ihrer Standardkonfigurationen:

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

Was den Standard festlegen würde, wenn er nirgendwo anders definiert wäre. In diesem Fall erhalten Sie mit OutputPath jedes Mal, wenn Sie eine Version erstellen, eine separate Assembly.

Erstellen Sie dann ein AfterBuild-Ziel, um Ihre verschiedenen Versionen zu kompilieren:

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

In diesem Beispiel wird das gesamte Projekt neu kompiliert, wobei die Framework-Variable nach dem ersten Build auf NET20 gesetzt wird (beide kompilieren und davon ausgehen, dass der erste Build das Standard-NET35 von oben war). Für jede Kompilierung werden die bedingten Definitionswerte korrekt festgelegt.

Auf diese Weise können Sie sogar bestimmte Dateien in der Projektdatei ausschließen, wenn Sie die Dateien nicht #ifdef müssen:

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

oder sogar Referenzen

<Reference Include="Some.Assembly" Condition="" '$(Framework)' == 'NET20' " >
  <HintPath>..\Lib\$(Framework)\Some.Assembly.dll</HintPath>
</Reference>
Todd
quelle
Perfekt. Ich hatte gerade genug Erfahrung mit dem Hacken des msbuild-Formats, um zu wissen, dass dies möglich ist, aber nicht genug Zeit, um alle Details herauszufinden. Vielen Dank!
McKamey
Wenn Sie in meiner verwandten Frage ( stackoverflow.com/questions/2923181 ) einen Verweis auf diese Antwort hinzufügen , werde ich Sie dort als Lösung markieren. Dies löst tatsächlich beide gleichzeitig.
McKamey
7
Vielen Dank für die Antwort, aber jetzt enthält VS2010 bereits ein neues Tag mit dem Namen "TargetFrameworkVersion". Jetzt wird für jede Eigenschaftsgruppe mit Bedingung nur TargetFrameworkVersion geändert. Müssen wir noch alle diese Tags verwenden, damit es funktioniert?
Akash Kava
Bei dieser Antwort geht es nicht nur darum, Konstanten für das Framework definiert zu haben, sondern auch darum, mehrere Frameworks zu
erstellen
4
Dieser Beitrag hat für mich funktioniert, aber ich bin nicht gut in MSBuild und es hat eine Weile gedauert, es herauszufinden. Ich habe ein Projekt gemacht, das als Beispiel dient. dev6.blob.core.windows.net/blog-images/DualTargetFrameworks.zip
TheDev6
44

Eine Alternative, die bisher für mich funktioniert, besteht darin, der Projektdatei Folgendes hinzuzufügen:

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

Dies nimmt den Wert der TargetFrameworkVersion-Eigenschaft an, die wie "v3.5" ist und die "v" und "" ersetzt. um "NET35" zu erhalten (unter Verwendung der neuen Eigenschaftsfunktionen Eigenschaftsfunktionen"). Anschließend werden alle vorhandenen "NETxx" -Werte entfernt und am Ende der DefinedConstants hinzugefügt. Es mag möglich sein, dies zu rationalisieren, aber ich habe nicht die Zeit, herumzuspielen.

Wenn Sie auf der Registerkarte Erstellen der Projekteigenschaften in VS nachsehen, wird der resultierende Wert im Abschnitt Symbole für bedingte Kompilierungen angezeigt. Durch Ändern der Ziel-Framework-Version auf der Registerkarte "Anwendung" wird das Symbol automatisch geändert. Sie können dann #if NETxxPräprozessor-Direktiven auf die übliche Weise verwenden. Das Ändern des Projekts in VS scheint die benutzerdefinierte PropertyGroup nicht zu verlieren.

Beachten Sie, dass dies für die Zieloptionen des Kundenprofils anscheinend nichts anderes ergibt, aber das ist für mich kein Problem.

Jeremy Cook
quelle
Jeremy, wow, danke, das ist perfekt, da ich meine Build-Lösung bereits separat einbaue.
Greg Finzer
+1. Wer hätte gedacht, dass es so schwer sein würde, "$ (DefineConstants.Contains ('..." ??) zu finden? Danke
CAD-Typ
Ich habe endlich wieder den Weg zu dieser Seite gefunden, weil ich eine Auffrischung brauchte, wie ich diese magischen Konstanten in meinen Build bekommen habe. Ich besuche heute dasselbe Projekt erneut, um die Bibliothek zu unterteilen, und ich brauche die Symbole, um mit mir in einige der Unterteilungen zu gehen. Ich habe nur darüber geschaut und festgestellt, dass Ihre Antwort in der ursprünglichen CSPROJ-Datei bereits ordnungsgemäß bestätigt wurde.
David A. Gray
15

Ich hatte Probleme mit diesen Lösungen, möglicherweise weil meine Anfangskonstanten durch diese Eigenschaften vorgefertigt wurden.

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

Visual Studio 2010 hat auch einen Fehler aufgrund der Semikolons ausgegeben und behauptet, es handele sich um illegale Zeichen. Die Fehlermeldung gab mir einen Hinweis, da ich die vorgefertigten Konstanten durch Kommas getrennt sehen konnte, gefolgt von meinem "illegalen" Semikolon. Nach einigen Neuformatierungen und Massagen konnte ich eine Lösung finden, die für mich funktioniert.

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

Ich würde einen Screenshot des Dialogfelds Erweiterte Compiler-Einstellungen veröffentlichen (geöffnet durch Klicken auf die Schaltfläche "Erweiterte Kompilierungsoptionen ..." auf der Registerkarte "Kompilieren" Ihres Projekts). Aber als neuer Benutzer fehlt mir der Repräsentant, um dies zu tun. Wenn Sie den Screenshot sehen könnten, würden Sie die benutzerdefinierten Konstanten sehen, die von der Eigenschaftsgruppe automatisch ausgefüllt werden, und dann würden Sie sagen: "Ich muss mir etwas davon besorgen."


EDIT: Habe diesen Repräsentanten überraschend schnell. Danke Jungs! Hier ist der Screenshot:

Erweiterte Compiler-Einstellungen

Nathaniel Roark
quelle
4

Beginnen Sie mit dem Löschen der Konstanten:

<PropertyGroup>
  <DefineConstants/>
</PropertyGroup>

Erstellen Sie als Nächstes Ihr Debug, Ihre Ablaufverfolgung und andere Konstanten wie:

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

Zuletzt erstellen Sie Ihre Framework-Konstanten:

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

Ich denke, dieser Ansatz ist sehr lesbar und verständlich.

zDougie
quelle
3

Fügen Sie in einer .csproj-Datei nach einer vorhandenen <DefineConstants>DEBUG;TRACE</DefineConstants>Zeile Folgendes hinzu:

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

Führen Sie dies sowohl für Debug- als auch für Release-Build-Konfigurationen aus. Dann verwenden Sie in Ihrem Code:

#if NET_40_OR_GREATER
   // can use dynamic, default and named parameters
#endif
Azarien
quelle
3
Standard- und benannte Parameter sind keine Funktion von .NET Framework 4, sondern eine Funktion des .NET 4-Compilers. Sie können auch in Projekten verwendet werden, die auf .NET 2 oder .NET 3 abzielen, sofern sie in Visual Studio 2010 kompiliert werden. Es handelt sich lediglich um syntaktischen Zucker. Auf der anderen Seite ist Dynamik eine Funktion von .NET Framework 4, die Sie zuvor nicht in Projekten verwenden können, die auf Frameworks abzielen.
Thanasis Ioannidis
2

@Azarien, Ihre Antwort kann mit der von Jeremy kombiniert werden, um sie an einem Ort zu speichern, anstatt Debug | Release usw.

Für mich funktioniert das Kombinieren beider Varianten am besten, dh das Einbeziehen von Bedingungen in Code mit #if NETXX und das gleichzeitige Erstellen für verschiedene Framework-Versionen.

Ich habe diese in meiner .csproj-Datei:

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

und in Zielen:

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

Wenn Sie das .NET Core-Buildsystem verwenden, können Sie dessen vordefinierte Symbole verwenden (die tatsächlich bereits zu Ihrem Beispiel passen und keine Änderungen an Ihrem erfordern .csproj!):

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

Die Liste der vordefinierten Symbole ist unter Entwickeln von Bibliotheken mit plattformübergreifenden Tools und #if (C # -Referenz) dokumentiert :

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

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

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

Kevinoid
quelle