在编译时检测目标框架版本

Posted

技术标签:

【中文标题】在编译时检测目标框架版本【英文标题】:Detect target framework version at compile time 【发布时间】:2011-03-27 01:51:30 【问题描述】:

我有一些使用扩展方法的代码,但使用 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 is defined in multiple places”警告。

当目标框架版本是 .NET 2 时,是否有任何编译时指令可用于仅包含 ExtensionAttribute?

【问题讨论】:

这里有一个讨论,可能是您需要的答案:***.com/questions/408908/…希望对您有所帮助! 另见***.com/questions/4535622/… 【参考方案1】:

带有“创建 N 个不同配置”的链接 SO 问题当然是一种选择,但是当我需要这个时,我只是添加了条件 DefineConstants 元素,所以在我的 Debug|x86(例如)中,在现有的 DefineConstants for DEBUG 之后;TRACE,我添加了这两个,检查在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 之间切换,它会做正确的事情。

【讨论】:

抱歉,我在哪里写那些 DefineConstants 标签? 破坏了#if DEBUG 预编译指令。马斯洛有可行的解决方案。不要提及 DEBUG;TRACE 常量,如果你只是想打破它们。 我知道这已经很老了,但 RUNNING 不会误导吗?不应该是COMPILING_ON_4吗? 这不适用于 v4.5,您需要在条件中添加 &amp;&amp; 以检查 4.5(如果这些条件支持 &amp;&amp; 运算符)。 如果您希望 IDE 默认使用的框架正确获取此属性(如果您未在项目中定义 TargetFramework),请确保将 DefineConstants 标记放在导入目标之后。 【参考方案2】:

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

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

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

    不会匹配 v4.5 或 v4.5.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>

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

使用编译时符号

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

参考文献

Property Functions

MSBuild Property Evaluation

Can I make a preprocessor directive dependent on the .NET framework version?

Conditional compilation depending on the framework version in C#

【讨论】:

这作为一个 NuGet 包会很棒! 这在 Visual Studio 2015 中不适用于 Visual Basic 项目。要修复,请将新的 Import before VisualBasic.Targets。 IE &lt;Import Project="MSBuild\VersionSpecificSymbols.Common.prop" /&gt; &lt;Import Project="$(MSBuildToolsPath32)\Microsoft.VisualBasic.targets"/&gt; VB 目标文件使用由DefineConstants 组成的属性FinalDefineConstants。因此,自定义导入必须首先发生为Properties are evaluated in the order in which they appear in the project file. 除了我更新了常量以匹配 2017 年的名称(NET451、NET45)等之外,它的工作就像一个魅力。【参考方案3】:

仅覆盖属性组,因此这会破坏您对 DEBUGTRACE 或任何其他设置的设置。 - 见MSBuild Property Evaluation

此外,如果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。

另请参阅:Common MsBuild Project Properties

【讨论】:

如果您在最后一行放置分号,则可以省略第三行:$(DefineConstants);$(CustomConstants)。如果常量变量为空,则额外的分号似乎不会导致任何问题。 仅供参考,这个结构对于 SharpDevelop IDE 来说有点太复杂了,会被它弄糊涂。 你甚至不需要有条件地这样做,你可以定义:&lt;DefineConstants&gt;NETFX$(TargetFrameworkVersion.Replace("v", "").Replace(".", "_"));$(DefineConstants)&lt;/DefineConstants&gt; 难以置信。你,以及几乎所有回答(或不回答)的人都会让 codeforlife 和 Girardi 等用户感到困惑(在 @JamesManning 的回答下)。在一年半的时间里,我在 C# 方面已经走得很远了,直到现在还没有听说过“propertygroups”,不管它们是什么。在这里,我发现了一群精英 C# 势利小人,他们的答案可能有用,但拒绝向我们中间明显困惑的人提供基本信息,以至少让我们开始了解您答案的基本要素。例如。什么是propertygroups?他们去哪里了?您的 GaryTrinder msdn 链接对此没有多大帮助。 DefineConstants (vs. CustomConstants) 元素不会覆盖我的“DEBUGTRACE 或任何其他设置”(至少在 v15 上使用 VS 2017。 8.1 在至少 C# WPF 应用程序上针对至少 3.5 或 4)。【参考方案4】:

目标框架的预定义符号现在内置在 MSBuild 版本中,dotnet 工具和 VS 2017 及更高版本使用该版本。有关完整列表,请参阅 https://docs.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

【讨论】:

很高兴知道 MS 终于有了解决方案,但除非他们在 .targets 文件中实现了这个并将它们部署在补丁中,否则这将无助于 VS2008 上的 OP 或任何仍然停留在 VS2015 上的人或更早。 此外,如果您的项目是从早期版本的 Visual Studio 迁移的,它们也不起作用。较早的答案描述了这一点以及如何对此进行调整。 它对我不起作用(至少在 v15.8.1 上至少使用 VS 2017,在至少针对至少 3.5 或 4 的 C# WPF 应用程序上使用)。也许是由于这个问题:“github.com/dotnet/docs/issues/7096”?注意:我的 "*.CsProj" 文件包含 "v3.5" 但 TargetFrameworkProfile 元素(我期望 @987654326 @要定义的预处理器符号)留空(如“”)。【参考方案5】:

我想提供一个可以解决一些问题的更新答案。

如果您设置 DefineConstants 而不是 CustomConstants,您最终会在 Conditional Compilation Symbols Debug 命令行中,在某些框架版本切换之后,使用重复的条件常量(即: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 (lrnz.ruggeri@gmail.com)
-->

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

【讨论】:

我无法看到您描述的重复符号问题。请提供更多细节。当您通过 UI 编辑自定义条件符号时,可能会发生这种情况?我已经看到了,但不明白 CustomConstants 如何避免这个问题。 最后,Choose ... When 的目的是什么。不是所有这些情况都已经在 else 元素中处理了吗? 是的,请回答上述@Andrew Dennison 的 2 个问题。我也有同样的问题。【参考方案6】:

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

这是我用于 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.

【讨论】:

以上是关于在编译时检测目标框架版本的主要内容,如果未能解决你的问题,请参考以下文章

编译 Java 应用程序时目标版本无效

测试编译给出“无效的目标版本:1.8”

#define 在目标构建设置的“用户定义”部分,在 xcode 编译代码时未定义

错误:从 Bookshelf.js save() 编译 SELECT 时检测到未定义的绑定

.NET 版本程序集是用(目标版本)编译的 [重复]

android编译时注解框架