web-dev-qa-db-fra.com

Détecter la version du framework cible au moment de la compilation

J'ai du code qui utilise des méthodes d'extension, mais compile sous .NET 2.0 en utilisant le compilateur dans VS2008. Pour faciliter cela, j'ai dû déclarer ExtensionAttribute:

/// <summary>
/// ExtensionAttribute is required to define extension methods under .NET 2.0
/// </summary>
public sealed class ExtensionAttribute : Attribute
{
}

Cependant, j'aimerais maintenant que la bibliothèque dans laquelle cette classe est contenue soit également compilable sous .NET 3.0, 3.5 et 4.0 - sans l'avertissement "ExtensionAttribute est défini à plusieurs endroits".

Existe-t-il une directive de temps de compilation que je peux utiliser pour inclure uniquement ExtensionAttribute lorsque la version du framework ciblée est .NET 2?

56
Matt Whitfield

La question liée SO avec "créer N configurations différentes" est certainement une option, mais quand j'en avais besoin, je viens d'ajouter des éléments conditionnels DefineConstants, donc dans mon Debug | x86 (par exemple) après les DefineConstants existants pour DEBUG; TRACE, j'ai ajouté ces 2, en vérifiant la valeur dans TFV qui a été définie dans le premier PropertyGroup du fichier csproj.

<DefineConstants Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">RUNNING_ON_4</DefineConstants>
<DefineConstants Condition=" '$(TargetFrameworkVersion)' != 'v4.0' ">NOT_RUNNING_ON_4</DefineConstants>

Vous n'avez pas besoin des deux, évidemment, mais il est juste là pour donner des exemples de comportement à la fois eq et ne - #else et #Elif fonctionnent bien aussi :)

class Program
{
    static void Main(string[] args)
    {
#if RUNNING_ON_4
        Console.WriteLine("RUNNING_ON_4 was set");
#endif
#if NOT_RUNNING_ON_4
        Console.WriteLine("NOT_RUNNING_ON_4 was set");
#endif
    }
}

Je pourrais alors basculer entre le ciblage 3.5 et 4.0 et cela ferait la bonne chose.

60
James Manning

J'ai quelques suggestions pour améliorer les réponses données jusqu'à présent:

  1. Utilisez Version.CompareTo (). Le test d'égalité ne fonctionnera pas pour les versions ultérieures du framework, mais n'a pas encore été nommé. Par exemple.

    <CustomConstants Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">
    

    ne correspondra pas à v4.5 ou v4.5.1, ce que vous voulez généralement.

  2. Utilisez un fichier d'importation pour que ces propriétés supplémentaires ne soient définies qu'une seule fois. Je recommande de garder le fichier d'import sous contrôle de source, afin que les modifications soient propagées avec les fichiers du projet, sans effort supplémentaire.

  3. Ajoutez l'élément d'importation à la fin de votre fichier de projet, afin qu'il soit indépendant de tout groupe de propriétés spécifique à la configuration. Cela présente également l'avantage d'exiger une seule ligne supplémentaire dans votre fichier de projet.

Voici le fichier d'importation (VersionSpecificSymbols.Common.prop)

<!--
******************************************************************
Defines the Compile time symbols Microsoft forgot
Modelled from https://msdn.Microsoft.com/en-us/library/ms171464.aspx
*********************************************************************
-->

<Project xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.5.1')))) &gt;= 0">$(DefineConstants);NETFX_451</DefineConstants>
        <DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.5'))))   &gt;= 0">$(DefineConstants);NETFX_45</DefineConstants>
        <DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.0'))))   &gt;= 0">$(DefineConstants);NETFX_40</DefineConstants>
        <DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('3.5'))))   &gt;= 0">$(DefineConstants);NETFX_35</DefineConstants>
        <DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('3.0'))))   &gt;= 0">$(DefineConstants);NETFX_30</DefineConstants>
    </PropertyGroup>
</Project>

Ajouter un élément d'importation au fichier de projet

Référencez-le à partir de votre fichier .csproj en ajoutant à la fin, avant la balise.

…
    <Import Project="VersionSpecificSymbols.Common.prop" />
</Project>

Vous devrez corriger le chemin pour pointer vers le dossier commun/partagé où vous placez ce fichier.

Pour utiliser les symboles de compilation

namespace VersionSpecificCodeHowTo
{
    using System;

    internal class Program
    {
        private static void Main(string[] args)
        {
#if NETFX_451
            Console.WriteLine("NET_451 was set");
#endif

#if NETFX_45
            Console.WriteLine("NET_45 was set");
#endif

#if NETFX_40
            Console.WriteLine("NET_40 was set");
#endif

#if NETFX_35
            Console.WriteLine("NETFX_35 was set");
#endif

#if NETFX_30
            Console.WriteLine("NETFX_30 was set");
#endif

#if NETFX_20
             Console.WriteLine("NETFX_20 was set");
#else
           The Version specific symbols were not set correctly!
#endif

#if DEBUG
            Console.WriteLine("DEBUG was set");
#endif

#if MySymbol
            Console.WriteLine("MySymbol was set");
#endif
            Console.ReadKey();
        }
    }
}

Un exemple courant de la "vraie vie"

Implémentation de Join (délimiteur de chaînes, chaînes IEnumerable) avant .NET 4.0

// string Join(this IEnumerable<string> strings, string delimiter)
// was not introduced until 4.0. So provide our own.
#if ! NETFX_40 && NETFX_35
public static string Join( string delimiter, IEnumerable<string> strings)
{
    return string.Join(delimiter, strings.ToArray());
}
#endif

Les références

Fonctions de propriété

Évaluation de la propriété MSBuild

Puis-je rendre une directive de préprocesseur dépendante de la version du framework .NET?

Compilation conditionnelle selon la version du framework en C #

33
Andrew Dennison

Les groupes de propriétés sont écrasés uniquement, ce qui entraînerait la suppression de vos paramètres pour DEBUG, TRACE ou tout autre. - Voir Évaluation de la propriété MSBuild

De plus, si la propriété DefineConstants est définie à partir de la ligne de commande, tout ce que vous lui faites dans le fichier de projet est sans importance car ce paramètre devient global en lecture seule. Cela signifie que vos modifications apportées à cette valeur échouent silencieusement.

Exemple de maintien des constantes définies existantes:

    <CustomConstants Condition=" '$(TargetFrameworkVersion)' == 'v2.0' ">V2</CustomConstants>
    <CustomConstants Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">V4</CustomConstants>
    <DefineConstants Condition=" '$(DefineConstants)' != '' And '$(CustomConstants)' != '' ">$(DefineConstants);</DefineConstants>
    <DefineConstants>$(DefineConstants)$(CustomConstants)</DefineConstants>

Cette section DOIT venir après toutes les autres constantes définies car il est peu probable qu'elles soient configurées de manière additive

J'ai seulement défini ces 2 parce que c'est surtout ce qui m'intéresse dans mon projet, ymmv.

Voir aussi: Propriétés communes du projet MsBuild

29
Maslow

Les symboles prédéfinis pour les frameworks cibles sont désormais intégrés dans la version de MSBuild qui est utilisée par l'outil dotnet et par VS 2017 et les versions ultérieures. Voir https://docs.Microsoft.com/en-us/dotnet/standard/frameworks#how-to-specify-target-frameworks pour la liste complète.

#if NET47
Console.WriteLine("Running on .Net 4.7");
#Elif NETCOREAPP2_0
Console.WriteLine("Running on .Net Core 2.0");
#endif
5
Arnavion

Je voudrais apporter une réponse mise à jour qui résout certains problèmes.

Si vous définissez DefineConstants au lieu de CustomConstants, vous vous retrouverez, dans la ligne de commande Débogage des symboles de compilation conditionnelle, après un changement de version de framework, avec des constantes conditionnelles dupliquées (par exemple: NETFX_451; NETFX_45; NETFX_40; NETFX_35; NETFX_30; NETFX_20; NETFX_35; NETFX_30 ; NETFX_20;). Il s'agit du VersionSpecificSymbols.Common.prop qui résout tout problème.

<!--
*********************************************************************
Defines the Compile time symbols Microsoft forgot
Modelled from https://msdn.Microsoft.com/en-us/library/ms171464.aspx
*********************************************************************
Author: Lorenzo Ruggeri ([email protected])
-->

<Project xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
  <Choose>
    <When Condition=" $(TargetFrameworkVersion) == 'v2.0' ">
      <PropertyGroup>
        <CustomConstants >$(CustomConstants);NETFX_20</CustomConstants>
      </PropertyGroup>
    </When>
    <When Condition=" $(TargetFrameworkVersion) == 'v3.0' ">
      <PropertyGroup>
        <CustomConstants >$(CustomConstants);NETFX_30</CustomConstants>
        <CustomConstants >$(CustomConstants);NETFX_20</CustomConstants>
      </PropertyGroup>
    </When>
    <When Condition=" $(TargetFrameworkVersion) == 'v3.5' ">
      <PropertyGroup>
        <CustomConstants >$(CustomConstants);NETFX_35</CustomConstants>
        <CustomConstants >$(CustomConstants);NETFX_30</CustomConstants>
        <CustomConstants >$(CustomConstants);NETFX_20</CustomConstants>
      </PropertyGroup>
    </When>
    <Otherwise>
      <PropertyGroup>
        <CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.5.1')))) &gt;= 0">$(CustomConstants);NETFX_451</CustomConstants>
        <CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.5')))) &gt;= 0">$(CustomConstants);NETFX_45</CustomConstants>
        <CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.0')))) &gt;= 0">$(CustomConstants);NETFX_40</CustomConstants>
        <CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('3.5')))) &gt;= 0">$(CustomConstants);NETFX_35</CustomConstants>
        <CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('3.0')))) &gt;= 0">$(CustomConstants);NETFX_30</CustomConstants>
        <CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('2.0')))) &gt;= 0">$(CustomConstants);NETFX_20</CustomConstants>
      </PropertyGroup>
    </Otherwise>
  </Choose>
  <PropertyGroup>
    <DefineConstants>$(DefineConstants);$(CustomConstants)</DefineConstants>
  </PropertyGroup>
</Project>
5
Lorenzo

Utilisez la réflexion pour déterminer si la classe existe. Si c'est le cas, alors dynamiquement créez et utilisez-le, sinon utilisez la classe de contournement .Net2 qui peut être définie, mais pas utilisée pour toutes les autres versions .net.

Voici le code que j'ai utilisé pour un AggregateException qui est .Net 4 et supérieur uniquement:

var aggregatException = Type.GetType("System.AggregateException");

if (aggregatException != null) // .Net 4 or greater
{
    throw ((Exception)Activator.CreateInstance(aggregatException, ps.Streams.Error.Select(err => err.Exception)));
}

// Else all other non .Net 4 or less versions
throw ps.Streams.Error.FirstOrDefault()?.Exception 
      ?? new Exception("Powershell Exception Encountered."); // Sanity check operation, should not hit.
1
ΩmegaMan