web-dev-qa-db-ja.com

コンパイル時にターゲットフレームワークのバージョンを検出する

拡張メソッドを使用するコードがいくつかありますが、VS2008のコンパイラを使用して.NET 2.0でコンパイルします。これを容易にするために、ExtensionAttributeを宣言する必要がありました。

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

ただし、そのクラスが含まれているライブラリは、.NET 3.0、3.5、および4.0でもコンパイルできるようになりました。「ExtensionAttributeは複数の場所で定義されています」という警告はありません。

対象となるフレームワークのバージョンが.NET 2の場合にのみExtensionAttributeを含めるために使用できるコンパイル時ディレクティブはありますか?

56
Matt Whitfield

リンクされたSO「N個の異なる構成を作成する」という質問は確かに1つのオプションですが、これが必要になったときは、DefineConstants要素を追加しただけなので、Debug | x86(たとえば) DEBUG; TRACEの既存のDefineConstantsの後に、csprojファイルの最初のPropertyGroupに設定されたTFVの値を確認するこれら2を追加しました。

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

当然、両方は必要ありませんが、eqとneの両方の動作の例を示すためにあります-#elseと#Elifもうまく機能します:)

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

その後、3.5と4.0をターゲットに切り替えることができ、それは正しいことをします。

60
James Manning

これまでの回答を改善するための提案がいくつかあります。

  1. Version.CompareTo()を使用します。同等性のテストは、今後のフレームワークバージョンでは機能しませんが、まだ名前が付けられていません。例えば。

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

    通常は必要なv4.5またはv4.5.1とは一致しません。

  2. インポートファイルを使用して、これらの追加プロパティを1回だけ定義する必要があるようにします。インポートファイルをソース管理下に置くことをお勧めします。これにより、変更がプロジェクトファイルとともに、余分な労力をかけずに伝達されます。

  3. プロジェクトファイルの最後にimport要素を追加して、構成固有のプロパティグループに依存しないようにします。これには、プロジェクトファイルに1行追加する必要があるという利点もあります。

インポートファイル(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>

インポート要素をプロジェクトファイルに追加

タグの前に追加して、.csprojファイルから参照します。

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

このファイルを配置したcommon/sharedフォルダーを指すようにパスを修正する必要があります。

コンパイル時間シンボルを使用するには

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();
        }
    }
}

一般的な「実生活」の例

.NET 4.0より前のJoin(string delimiter、IEnumerable strings)の実装

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

参照資料

プロパティ関数

MSBuildプロパティの評価

。NETフレームワークバージョンに依存するプリプロセッサディレクティブを作成できますか?

C#のフレームワークバージョンに応じた条件付きコンパイル

33
Andrew Dennison

プロパティグループは上書きのみであるため、DEBUGTRACE、またはその他の設定が無効になります。 - MSBuildプロパティの評価 を参照

また、DefineConstantsプロパティがコマンドラインから設定されている場合、プロジェクトファイル内で行うことは、その設定がグローバルな読み取り専用になるため、関係ありません。これは、その値への変更が静かに失敗することを意味します。

既存の定義済み定数を維持する例:

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

このセクションは、他の定義済み定数の後に来る必要があります。これらは付加的な方法で設定される可能性が低いためです

私はこれらの2つだけを定義しました。なぜなら、それがほとんど私のプロジェクトで興味があるものであるからです。ymmv。

関連項目: 共通のMsBuildプロジェクトプロパティ

29
Maslow

ターゲットフレームワークの事前定義されたシンボルは、dotnetツールおよびVS 2017以降で使用されるMSBuildのバージョンに組み込まれています。完全なリストについては、 https://docs.Microsoft.com/en-us/dotnet/standard/frameworks#how-to-specify-target-frameworks を参照してください。

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

いくつかの問題を解決する最新の回答を提供したいと思います。

CustomConstantsの代わりにDefineConstantsを設定すると、いくつかのフレームワークバージョンを切り替えた後、条件付きコンパイルシンボルデバッグコマンドラインで、条件付き定数が重複します(つまり、NETFX_451; NETFX_45; NETFX_40; NETFX_35; NETFX_30; NETFX_20; NETFX_35; NETFX_30 ; NETFX_20;)。これは、問題を解決するVersionSpecificSymbols.Common.propです。

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

リフレクションを使用して、クラスが存在するかどうかを判断します。存在する場合は、動的に作成して使用します。そうでない場合は、定義できますが、他のすべての.netバージョンには使用されない.Net2回避策クラスを使用します。

以下は、.Net 4以上のAggregateExceptionに使用したコードです。

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