编译时检测目标框架版本

问题描述 投票:0回答:7

我有一些使用扩展方法的代码,但使用 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?

c# .net visual-studio msbuild extension-methods
7个回答
65
投票

带有“创建 N 个不同配置”的链接 SO 问题当然是一种选择,但是当我需要这个时,我只是添加了条件 DefineConstants 元素,因此在我的 Debug|x86 (例如)中,在 DEBUG;TRACE 的现有 DefineConstants 之后,我添加了这 2 个,检查在 csproj 文件的第一个 PropertyGroup 中设置的 TFV 中的值。

<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 之间切换,它会做正确的事情。


40
投票

我有一些改进目前给出的答案的建议:

  1. 使用 Version.CompareTo()。相等性测试不适用于尚未命名的更高版本的框架。例如

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

    与您通常想要的 v4.5 或 v4.5.1 不匹配。

  2. 使用导入文件,这样这些附加属性只需定义一次。我建议将导入文件置于源代码控制之下,以便更改与项目文件一起传播,而不需要额外的努力。

  3. 在项目文件的末尾添加导入元素,以便它独立于任何配置特定的属性组。这还有一个好处是需要在项目文件中添加一行。

这是导入文件(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>

您需要修复路径以指向放置此文件的公共/共享文件夹。

使用编译时符号

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(字符串分隔符,IEnumerable字符串)

// 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 Framework 版本制作预处理器指令吗?

C#中根据框架版本进行条件编译


30
投票

属性组仅被覆盖,因此这会取消您对

DEBUG
TRACE
或任何其他设置的设置。 - 请参阅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>

本节必须位于任何其他定义的常量之后,因为这些常量不太可能以附加方式设置

我只定义了这两个,因为这是我对我的项目最感兴趣的,ymmv。

另请参阅:常见的 MsBuild 项目属性


20
投票

目标框架的预定义符号现已内置到

dotnet
工具和 VS 2017 及以后版本使用的 MSBuild 版本中。请参阅 https://learn.microsoft.com/en-us/dotnet/standard/frameworks#how-to-specify-a-target-framework 了解完整列表。

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

5
投票

我想贡献一个更新的答案来解决一些问题。

如果您设置 DefineConstants 而不是 CustomConstants,在进行某些框架版本切换后,您最终会在条件编译符号调试命令行中出现重复的条件常量(即: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>

1
投票

使用反射来判断类是否存在。如果是,则动态地创建并使用它,否则使用可以定义的.Net2解决方法类,但不用于所有其他.net版本。

这是我用于

AggregateException
的代码,仅适用于 .Net 4 及更高版本:

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.

0
投票

适用于 .NET 5+(和 .NET Core)

您可以在编译时使用

Preprocessor symbols
,更有趣的一点是也可以使用
GREATER
版本:

#if NET8_0_OR_GREATER
Console.WriteLine("Running on .Net 8 or greater versions");
#elif NET6_0_OR_GREATER
Console.WriteLine("Running on .Net 6 or greater versions");
#elif NETCOREAPP3_1_OR_GREATER
Console.WriteLine("Running on .Net Core 3.1 or greater versions");
#endif

在此处查看更多选项:

https://learn.microsoft.com/en-us/dotnet/standard/frameworks#preprocessor-symbols

© www.soinside.com 2019 - 2024. All rights reserved.