简体   繁体   English

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

[英]Detect target framework version at compile time

I have some code which makes use of Extension Methods, but compiles under .NET 2.0 using the compiler in VS2008.我有一些使用扩展方法的代码,但使用 VS2008 中的编译器在 .NET 2.0 下编译。 To facilitate this, I had to declare ExtensionAttribute:为此,我必须声明 ExtensionAttribute:

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

However, I'd now like the library in which that class is contained to also be compilable under .NET 3.0, 3.5 and 4.0 - without the 'ExtensionAttribute is defined in multiple places' warning.但是,我现在希望包含该类的库也可以在 .NET 3.0、3.5 和 4.0 下编译——没有“ExtensionAttribute 在多个地方定义”警告。

Is there any compile time directive I can use to only include the ExtensionAttribute when the framework version being targetted is .NET 2?当目标框架版本是 .NET 2 时,是否可以使用任何编译时指令来仅包含 ExtensionAttribute?

The linked SO question with 'create N different configurations' is certainly one option, but when I had a need for this I just added conditional DefineConstants elements, so in my Debug|x86 (for instance) after the existing DefineConstants for DEBUG;TRACE, I added these 2, checking the value in TFV that was set in the first PropertyGroup of the csproj file.带有“创建 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>

You don't need both, obviously, but it's just there to give examples of both eq and ne behavior - #else and #elif work fine too:)显然,您不需要两者,但它只是为了提供 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
    }
}

I could then switch between targeting 3.5 and 4.0 and it would do the right thing.然后我可以在目标 3.5 和 4.0 之间切换,它会做正确的事情。

I have a few suggestions for improving on the answers given so far:我有一些改进目前给出的答案的建议:

  1. Use Version.CompareTo().使用 Version.CompareTo()。 Testing for equality will not work for later framework versions, yet to be named.相等性测试不适用于更高版本的框架,尚未命名。 Eg例如

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

    will not match v4.5 or v4.5.1, which typically you do want.不会匹配 v4.5 或 v4.5.1,这通常是您想要的。

  2. Use an import file so that these additional properties only need to be defined once.使用导入文件,这样这些附加属性只需定义一次。 I recommend keeping the imports file under source control, so that changes are propagated along with the project files, without extra effort.我建议将导入文件置于源代码控制之下,以便更改与项目文件一起传播,无需额外的努力。

  3. Add the import element at the end of your project file, so that it is independent of any configuration specific property groups.在项目文件的末尾添加 import 元素,以便它独立于任何特定于配置的属性组。 This also has the benefit of requiring a single additional line in your project file.这还有一个好处,即在您的项目文件中需要一个额外的行。

Here is the import file (VersionSpecificSymbols.Common.prop)这是导入文件 (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>

Add Import Element to Project File将导入元素添加到项目文件

Reference it from your.csproj file by adding at the end, before the tag.通过在标记之前的末尾添加,从您的 .csproj 文件中引用它。

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

You will need to fix up the path to point to the common/shared folder where you put this file.您将需要修复路径以指向放置此文件的公共/共享文件夹。

To Use Compile Time Symbols使用编译时符号

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

A Common “Real Life” Example一个常见的“现实生活”例子

Implementing Join(string delimiter, IEnumerable strings) Prior to .NET 4.0在 .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

References参考

Property Functions 属性函数

MSBuild Property Evaluation MSBuild 属性评估

Can I make a preprocessor directive dependent on the .NET framework version? 我可以根据 .NET Framework 版本创建预处理器指令吗?

Conditional compilation depending on the framework version in C# 根据 C# 中的框架版本进行条件编译

Property groups are overwrite only so this would knock out your settings for DEBUG , TRACE , or any others.属性组仅被覆盖,因此这会破坏您对DEBUGTRACE或任何其他设置的设置。 - See MSBuild Property Evaluation - 请参阅MSBuild 属性评估

Also if the DefineConstants property is set from the command line anything you do to it inside the project file is irrelevant as that setting becomes global readonly.此外,如果DefineConstants属性是从命令行设置的,那么您在项目文件中对其所做的任何操作都是无关紧要的,因为该设置将变为全局只读。 This means your changes to that value fail silently.这意味着您对该值的更改会悄无声息地失败。

Example maintaining existing defined constants:维护现有定义常量的示例:

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

This section MUST come after any other defined constants since those are unlikely to be set up in an additive manner此部分必须位于任何其他定义的常量之后,因为这些常量不太可能以附加方式设置

I only defined those 2 because that's mostly what I'm interested in on my project, ymmv.我只定义了这 2 个,因为这主要是我对我的项目 ymmv 感兴趣的内容。

See Also: Common MsBuild Project Properties另请参阅:常见的 MsBuild 项目属性

Pre-defined symbols for target frameworks are now built into the version of MSBuild that is used by the dotnet tool and by VS 2017 onwards.目标框架的预定义符号现在内置到dotnet工具和 VS 2017 之后使用的 MSBuild 版本中。 See https://learn.microsoft.com/en-us/dotnet/standard/frameworks#how-to-specify-a-target-framework for the full list.有关完整列表,请参阅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

I would like to contribute with an updated answer which solves some issues.我想提供一个更新的答案来解决一些问题。

If you set DefineConstants instead of CustomConstants you'll end up, in the Conditional Compilation Symbols Debug command line, after some framework version switch, with duplicated conditional constants (ie: NETFX_451;NETFX_45;NETFX_40;NETFX_35;NETFX_30;NETFX_20;NETFX_35;NETFX_30;NETFX_20;).如果你设置 DefineConstants 而不是 CustomConstants 你最终会在条件编译符号调试命令行中,在一些框架版本切换之后,使用重复的条件常量(即:NETFX_451;NETFX_45;NETFX_40;NETFX_35;NETFX_30;NETFX_20;NETFX_35;NETFX_30 ;NETFX_20;)。 This is the VersionSpecificSymbols.Common.prop which solves any issue.这是解决任何问题的 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>

Use reflection to determine if the class exists.使用反射来判断类是否存在。 If it does, then dynamically create and use it, otherwise use the.Net2 workaround class which can be defined, but not used for all other .net versions.如果是,则动态创建并使用它,否则使用可以定义但不能用于所有其他 .net 版本的 .Net2 解决方法类。

Here is code I used for an AggregateException which is.Net 4 and greater only:这是我用于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.

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM