条件编译和框架目标

130

如果目标框架是较新版本,我的项目中有一些代码可能需要进行重大改进。我希望能够更好地利用C#中的条件编译来根据需要进行切换。

类似于:

#if NET40
using FooXX = Foo40;
#elif NET35
using FooXX = Foo35;
#else NET20
using FooXX = Foo20;
#endif

这些符号中有哪些是免费提供的?我需要将这些符号作为项目配置的一部分注入吗?似乎很容易做到,因为我可以从MSBuild中知道目标框架。

/p:DefineConstants="NET40"
人们是如何处理这种情况的?您是否创建了不同的配置?您是否通过命令行传递常量?

人们是如何处理这种情况的?您是否创建了不同的配置?您是否通过命令行传递常量?


如果您想在VS中使用简单的预制解决方案,请为此用户投票,http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/7699920-built-in-conditional-compilation-by-framework-vers。 - JohnC
1
也可以看看这个链接。非常详细。 http://blogs.msmvps.com/punitganshani/2015/06/21/5-steps-to-targeting-multiple-net-frameworks/ - Marco Alves
项目组、NuGet 恢复和 NuGet 引用组,不错的解决方案:https://shazwazza.com/post/Multi-targeting-a-single-Net-project-to-build-for-different-framework-versions - OzBob
7个回答

121
创建不同的构建配置是实现这一目标的最佳方法之一。
<PropertyGroup Condition="  '$(Framework)' == 'NET20' ">
  <DefineConstants>NET20</DefineConstants>
  <OutputPath>bin\$(Configuration)\$(Framework)</OutputPath>
</PropertyGroup>


<PropertyGroup Condition="  '$(Framework)' == 'NET35' ">
  <DefineConstants>NET35</DefineConstants>
  <OutputPath>bin\$(Configuration)\$(Framework)</OutputPath>
</PropertyGroup>

而且在你的默认配置中之一:

<Framework Condition=" '$(Framework)' == '' ">NET35</Framework>

如果在其他地方没有定义,默认情况下会设置哪个。在上述情况中,OutputPath将为您每次构建每个版本提供一个单独的程序集。
然后创建一个AfterBuild目标来编译您的不同版本:
<Target Name="AfterBuild">
  <MSBuild Condition=" '$(Framework)' != 'NET20'"
    Projects="$(MSBuildProjectFile)"
    Properties="Framework=NET20"
    RunEachTargetSeparately="true"  />
</Target>

这个例子将在第一次构建后重新编译整个项目,并将Framework变量设置为NET20(假设第一次构建是上面默认的NET35)。每次编译都会正确设置条件定义值。
通过这种方式,即使你想要排除项目文件中的某些文件,也不需要在文件中使用#ifdef。
<Compile Include="SomeNet20SpecificClass.cs" Condition=" '$(Framework)' == 'NET20' " />

或者甚至是参考资料

<Reference Include="Some.Assembly" Condition=" '$(Framework)' == 'NET20' " >
  <HintPath>..\Lib\$(Framework)\Some.Assembly.dll</HintPath>
</Reference>

7
谢谢您的回答,但是现在VS2010已经包括了一个名为“TargetFrameworkVersion”的新标签,现在对于每个带条件的属性组,只需要更改TargetFrameworkVersion,我们还需要做所有这些才能使其工作吗? - Akash Kava
4
这篇文章对我很有帮助,但我不太擅长 MSBuild,花了一些时间才理解。我制作了一个项目作为示例。https://dev6.blob.core.windows.net/blog-images/DualTargetFrameworks.zip - TheDev6
我刚刚在VS 2013中将这项技术应用到了两个项目中。太棒了! - David A. Gray
在使用VS构建时对我有用。但是,通过NAnt脚本调用MSBuild构建.sln时,在<AfterBuild>中的第二次构建会复制第一次构建的输出。构建日志显示“跳过目标“CoreCompile”,因为所有输出文件都与输入文件保持最新。”我认为MSBuild决定只复制第一次构建的输出,因为它认为没有任何变化。我通过在项目中的一个文件的<MSBuild>行之前添加<AfterBuild> <Touch Files="File.cs" />来解决这个问题。这迫使MSBuild重新构建第二次构建。 - Dude0001
也可以看一下这个链接。非常详细。http://blogs.msmvps.com/punitganshani/2015/06/21/5-steps-to-targeting-multiple-net-frameworks/ - Marco Alves
显示剩余5条评论

46

到目前为止,对我有效的替代方法是将以下内容添加到项目文件中:

 <PropertyGroup>
    <DefineConstants Condition=" !$(DefineConstants.Contains(';NET')) ">$(DefineConstants);$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", ""))</DefineConstants>
    <DefineConstants Condition=" $(DefineConstants.Contains(';NET')) ">$(DefineConstants.Remove($(DefineConstants.LastIndexOf(";NET"))));$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", ""))</DefineConstants>
  </PropertyGroup>

这将获取TargetFrameworkVersion属性的值,类似于"v3.5",替换"v"和"."以获得"NET35"(使用新的Property Functions功能)。然后删除任何现有的"NETxx"值并将其添加到DefinedConstants的末尾。可能可以简化此过程,但我没有时间调整。
在VS项目属性的Build选项卡中查看条件编译符号部分中的结果值。更改应用程序选项卡上的目标框架版本会自动更改该符号。然后,您可以像往常一样使用#if NETxx预处理指令。在VS中更改项目似乎不会丢失自定义PropertyGroup。
请注意,这似乎对客户端配置文件目标选项没有任何不同,但这对我来说不是问题。

Jeremy,哇谢谢你,这太完美了,因为我已经在我的构建解决方案中单独进行了构建。 - Greg Finzer
+1. 谁会想到找"$(DefineConstants.Contains('..."这么难啊?谢谢。 - CAD bloke
我终于又找到了这个页面,因为我需要复习一下我是如何将这些魔术常量加入我的构建中的。今天我再次回顾同一个项目,将库进行分割,而我需要这些符号跟随我进入某些子部分。我刚才查了一下,注意到你的答案已经在原始的.CSPROJ文件中得到了正式确认。 - David A. Gray

16

可能是因为我的初始常量是由这些属性预先构建的,所以我遇到了这些解决方案的问题。

<DefineConstants />
<DefineDebug>true</DefineDebug>
<DefineTrace>true</DefineTrace>
<DebugSymbols>true</DebugSymbols>

Visual Studio 2010也因为分号而报错,声称它们是非法字符。错误提示让我有了一个线索,因为我可以看到用逗号分隔的预定义常量,最终跟着我的“非法”分号。经过一些重新格式化和调整,我能够得出一个对我有效的解决方案。

<PropertyGroup>
  <!-- Adding a custom constant will auto-magically append a comma and space to the pre-built constants.    -->
  <!-- Move the comma delimiter to the end of each constant and remove the trailing comma when we're done.  -->
  <DefineConstants Condition=" !$(DefineConstants.Contains(', NET')) ">$(DefineConstants)$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", "")), </DefineConstants>
  <DefineConstants Condition=" $(DefineConstants.Contains(', NET')) ">$(DefineConstants.Remove($(DefineConstants.LastIndexOf(", NET"))))$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", "")), </DefineConstants>
  <DefineConstants Condition=" $(TargetFrameworkVersion.Replace('v', '')) >= 2.0 ">$(DefineConstants)NET_20_OR_GREATER, </DefineConstants>
  <DefineConstants Condition=" $(TargetFrameworkVersion.Replace('v', '')) >= 3.5 ">$(DefineConstants)NET_35_OR_GREATER</DefineConstants>
  <DefineConstants Condition=" $(DefineConstants.EndsWith(', ')) ">$(DefineConstants.Remove($(DefineConstants.LastIndexOf(", "))))</DefineConstants>
</PropertyGroup>

我想发布一个高级编译器设置对话框的截图(通过在项目的“编译”选项卡上单击“高级编译选项...”按钮打开)。但作为新用户,我缺乏足够的声望来这样做。如果您能看到屏幕截图,您将会看到属性组自动填充的自定义常量,然后你会说,“我得去弄些这个东西。”


编辑: 声望出奇地快..谢谢大家! 这就是那张截图:

高级编译器设置


11

如果您正在使用.NET Core构建系统,可以使用其预定义的符号(实际上已经与您的示例匹配,不需要对您的.csproj进行任何更改!):

#if NET40
using FooXX = Foo40;
#elif NET35
using FooXX = Foo35;
#else NET20
using FooXX = Foo20;
#endif

预定义符号列表的文档在使用跨平台工具开发库#if (C# 参考)中有记录:

.NET Framework: NETFRAMEWORK, NET20, NET35, NET40, NET45, NET451, NET452, NET46, NET461, NET462, NET47, NET471, NET472, NET48

.NET Standard: NETSTANDARD, NETSTANDARD1_0, NETSTANDARD1_1, NETSTANDARD1_2, NETSTANDARD1_3, NETSTANDARD1_4, NETSTANDARD1_5, NETSTANDARD1_6, NETSTANDARD2_0, NETSTANDARD2_1

.NET Core: NETCOREAPP, NETCOREAPP1_0, NETCOREAPP1_1, NETCOREAPP2_0, NETCOREAPP2_1, NETCOREAPP2_2, NETCOREAPP3_0


4

首先清除常量:

<PropertyGroup>
  <DefineConstants/>
</PropertyGroup>

接下来,建立你的调试、跟踪和其他常量,如下所示:
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
    <DebugSymbols>true</DebugSymbols>
  <DebugType>full</DebugType>
  <Optimize>false</Optimize>
  <DefineConstants>TRACE;DEBUG;$(DefineConstants)</DefineConstants>
</PropertyGroup>

最后,建立您的框架常量:
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v2.0' ">
  <DefineConstants>NET10;NET20;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v3.0' ">
  <DefineConstants>NET10;NET20;NET30;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v3.5' ">
  <DefineConstants>NET10;NET20;NET30;NET35;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">
  <DefineConstants>NET10;NET20;NET30;NET35;NET40;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v4.5' ">
  <DefineConstants>NET10;NET20;NET30;NET35;NET40;NET45;$(DefineConstants)</DefineConstants>
</PropertyGroup>

我认为这种方法非常易读易懂。


3
在 .csproj 文件中,在现有的 <DefineConstants>DEBUG;TRACE</DefineConstants> 行之后,添加以下内容:
<DefineConstants Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' &gt;= '4.0' ">NET_40_OR_GREATER</DefineConstants>
<DefineConstants Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' == '4.0' ">NET_40_EXACTLY</DefineConstants>

对于Debug和Release构建配置都要这样做。 然后在你的代码中使用:

#if NET_40_OR_GREATER
   // can use dynamic, default and named parameters
#endif

3
默认参数和命名参数不是 .NET Framework 4 的功能,而是 .NET 4 编译器的功能。只要在 Visual Studio 2010 中编译,它们也可以用于针对 .NET 2 或 .NET 3 的项目。这只是一种语法糖。另一方面,动态类型是 .NET Framework 4 的一个功能,您无法在针对此之前的框架的项目中使用它。 - Thanasis Ioannidis

2

@Azarien,你的回答可以和Jeremy的结合在一起,而不是Debug|Release等。

对我来说,最好的方法是将两种变化结合起来,即在代码中使用#if NETXX条件,并同时构建不同的框架版本。

我在我的.csproj文件中有以下内容:

  <PropertyGroup>
    <DefineConstants Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' &gt;= '4.0' ">NET_40_OR_GREATER</DefineConstants>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' == '3.5' ">
    <DefineConstants>NET35</DefineConstants>
    <OutputPath>bin\$(Configuration)\$(TargetFrameworkVersion)</OutputPath>
  </PropertyGroup>

并且在目标中:

  <Target Name="AfterBuild">
    <MSBuild Condition=" '$(TargetFrameworkVersion.Replace(&quot;v&quot;,&quot;&quot;))' &gt;= '4.0' "
      Projects="$(MSBuildProjectFile)"
      Properties="TargetFrameworkVersion=v3.5"
      RunEachTargetSeparately="true"  />
  </Target>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接