Compilation conditionnelle et objectifs du cadre

124

Il y a quelques endroits mineurs où le code de mon projet peut être considérablement amélioré si le framework cible était une version plus récente. J'aimerais pouvoir mieux tirer parti de la compilation conditionnelle en C # pour les changer selon les besoins.

Quelque chose comme:

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

L'un de ces symboles est-il gratuit? Dois-je injecter ces symboles dans le cadre de la configuration du projet? Cela semble assez facile à faire puisque je saurai quel framework est ciblé par MSBuild.

/p:DefineConstants="NET40"

Comment les gens gèrent-ils cette situation? Créez-vous différentes configurations? Passez-vous les constantes via la ligne de commande?

Mckamey
la source
Si vous voulez une solution précuite simple dans VS, veuillez voter pour cette voix d'utilisateur, visualstudio.uservoice.com/forums/121579-visual-studio/… .
JohnC
1
Jetez également un œil à ce lien. Assez explicatif. blogs.msmvps.com/punitganshani/2015/06/21/…
Marco Alves
groupes de projet, restauration de nuget et groupes de référence nuget, solution intéressante: shazwazza.com/post/…
OzBob

Réponses:

119

L'un des meilleurs moyens d'y parvenir est de créer différentes configurations de build dans votre projet:

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

Et dans l'une de vos configurations par défaut:

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

Ce qui définirait la valeur par défaut si elle n'était définie nulle part ailleurs. Dans le cas ci-dessus, OutputPath vous donnera un assembly distinct à chaque fois que vous construisez chaque version.

Créez ensuite une cible AfterBuild pour compiler vos différentes versions:

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

Cet exemple recompilera l'intégralité du projet avec la variable Framework définie sur NET20 après la première génération (en compilant les deux et en supposant que la première génération était le NET35 par défaut ci-dessus). Chaque compilation aura les valeurs de définition conditionnelles définies correctement.

De cette manière, vous pouvez même exclure certains fichiers du fichier projet si vous voulez sans avoir à #ifdef les fichiers:

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

ou même des références

<Reference Include="Some.Assembly" Condition="" '$(Framework)' == 'NET20' " >
  <HintPath>..\Lib\$(Framework)\Some.Assembly.dll</HintPath>
</Reference>
Todd
la source
Parfait. J'avais juste assez d'expérience dans le piratage du format msbuild pour savoir que cela pouvait être fait, mais pas assez de temps pour comprendre tous les détails. Merci beaucoup!
mckamey
Si vous ajoutez une référence à cette réponse sur ma question connexe ( stackoverflow.com/questions/2923181 ), je vous marquerai comme la solution ici. Cela résout en fait les deux en même temps.
mckamey
7
Merci pour la réponse, mais maintenant VS2010 inclut déjà une nouvelle balise nommée "TargetFrameworkVersion", maintenant pour chaque groupe de propriétés avec condition, seule TargetFrameworkVersion est modifiée, avons-nous encore besoin de tout cela pour le faire fonctionner?
Akash Kava
Cette réponse ne concerne pas seulement la définition de constantes pour le framework, mais également la création de plusieurs frameworks
Katbyte
4
Ce poste a fonctionné pour moi mais je ne suis pas bon chez MSBuild et il a fallu un certain temps pour le comprendre. J'ai fait un projet qui fonctionne comme un exemple. dev6.blob.core.windows.net/blog-images/DualTargetFrameworks.zip
TheDev6
44

Une alternative qui fonctionne pour moi jusqu'à présent est d'ajouter ce qui suit au fichier de projet:

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

Cela prend la valeur de la propriété TargetFrameworkVersion, qui est comme "v3.5", remplace les "v" et "." pour obtenir "NET35" (en utilisant la nouvelle fonctionnalité Property Functions ). Il supprime ensuite toute valeur «NETxx» existante et l'ajoute à la fin des DefinedConstants. Il est peut-être possible de rationaliser cela, mais je n'ai pas le temps de jouer.

En regardant dans l'onglet Générer des propriétés du projet dans VS, vous verrez la valeur résultante dans la section des symboles de compilation conditionnelle. La modification de la version du framework cible dans l'onglet Application modifie alors automatiquement le symbole. Vous pouvez ensuite utiliser les #if NETxxdirectives de préprocesseur de la manière habituelle. La modification du projet dans VS ne semble pas perdre le PropertyGroup personnalisé.

Notez que cela ne semble pas vous donner quelque chose de différent pour les options cibles du profil client, mais ce n'est pas un problème pour moi.

Jeremy Cook
la source
Jeremy, wow merci c'est parfait car je construis déjà séparément dans ma solution de build.
Greg Finzer
+1. Qui aurait pensé qu'il serait si difficile de trouver "$ (DefineConstants.Contains ('..." ?? Thanks
CAD bloke
J'ai finalement retrouvé mon chemin vers cette page, car j'avais besoin d'un rappel sur la façon dont j'avais ces constantes magiques dans ma construction. Je revisite aujourd'hui le même projet, pour subdiviser la bibliothèque, et j'ai besoin des symboles pour m'accompagner dans certaines des subdivisions. J'ai juste regardé au-dessus et j'ai remarqué que votre réponse est déjà dûment reconnue dans le fichier .CSPROJ original.
David A. Gray
15

J'ai eu des problèmes avec ces solutions, probablement parce que mes constantes initiales étaient pré-construites par ces propriétés.

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

Visual Studio 2010 a également généré une erreur en raison des points-virgules, affirmant qu'il s'agissait de caractères illégaux. Le message d'erreur m'a donné un indice car je pouvais voir les constantes pré-construites séparées par des virgules, suivies finalement de mon point-virgule "illégal". Après quelques reformatages et massages, j'ai pu trouver une solution qui fonctionne pour moi.

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

Je publierais une capture d'écran de la boîte de dialogue Paramètres avancés du compilateur (ouverte en cliquant sur le bouton "Options de compilation avancées ..." sur l'onglet Compiler de votre projet). Mais en tant que nouvel utilisateur, je n'ai pas le représentant pour le faire. Si vous pouviez voir la capture d'écran, vous verriez les constantes personnalisées remplies automatiquement par le groupe de propriétés, puis vous diriez: "Je dois m'en procurer une partie".


EDIT: J'ai obtenu ce représentant étonnamment rapide .. Merci les gars! Voici cette capture d'écran:

Paramètres avancés du compilateur

Nathaniel Roark
la source
4

Commencez par effacer les constantes:

<PropertyGroup>
  <DefineConstants/>
</PropertyGroup>

Ensuite, construisez votre débogage, trace et autres constantes comme:

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

Enfin, créez vos constantes de cadre:

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

Je pense que cette approche est très lisible et compréhensible.

zDougie
la source
3

Dans un fichier .csproj, après une <DefineConstants>DEBUG;TRACE</DefineConstants>ligne existante , ajoutez ceci:

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

Faites ceci pour les configurations de build Debug et Release. Ensuite, utilisez dans votre code:

#if NET_40_OR_GREATER
   // can use dynamic, default and named parameters
#endif
Azarien
la source
3
Les paramètres par défaut et nommés ne sont pas une fonctionnalité de .NET Framework 4, mais une fonctionnalité du compilateur .NET 4. Ils peuvent également être utilisés dans des projets ciblant .NET 2 ou .NET 3 à condition qu'ils soient compilés dans Visual Studio 2010. Ce n'est que du sucre syntaxique. D'autre part, la dynamique est une fonctionnalité de .NET Framework 4 et vous ne pouvez pas l'utiliser dans des projets ciblant des frameworks antérieurs.
Thanasis Ioannidis
2

@Azarien, votre réponse peut être combinée avec celle de Jeremy pour la conserver au même endroit plutôt que Debug | Release etc.

Pour moi, combiner les deux variantes fonctionne mieux, c'est-à-dire inclure des conditions dans le code en utilisant #if NETXX et également construire pour différentes versions de framework en une seule fois.

J'ai ces derniers dans mon fichier .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>

et en cibles:

  <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
la source
0

Si vous utilisez le système de construction .NET Core, vous pouvez utiliser ses symboles prédéfinis (qui correspondent déjà à votre exemple et ne nécessitent aucune modification de votre .csproj!):

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

La liste des symboles prédéfinis est documentée dans Développement de bibliothèques avec des outils multiplateformes et #if (Référence C #) :

.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 de base: NETCOREAPP , NETCOREAPP1_0, NETCOREAPP1_1, NETCOREAPP2_0, NETCOREAPP2_1, NETCOREAPP2_2,NETCOREAPP3_0

Kevinoïde
la source