条件编译和框架目标


124

如果目标框架是较新的版本,则可以在几个较小的地方极大地改进我的项目代码。我希望能够更好地利用C#中的条件编译来根据需要切换这些条件。

就像是:

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

这些符号中的任何一个免费吗?我是否需要在项目配置中注入这些符号?这样做似乎很容易,因为我将了解MSBuild所针对的框架。

/p:DefineConstants="NET40"

人们如何处理这种情况?您要创建其他配置吗?您是通过命令行传递常量吗?



如果您想在VS中使用简单的预烘焙解决方案,请对此用户声音visualstudio.uservoice.com/forums/121579-visual-studio/…进行投票。
JohnC 2015年

1
也看一下此链接。很好解释。 blogs.msmvps.com/punitganshani/2015/06/21/...
马尔科·阿尔维斯

项目组,nuget恢复和nuget参考组,不错的解决方案:shazwazza.com/post/…–
OzBob

Answers:


119

最好的方法之一是在项目中创建不同的构建配置:

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

完善。我只有足够的经验来破解msbuild格式才能知道它可以完成,但是没有足够的时间弄清楚所有细节。非常感谢你!
mckamey,2010年

如果您在我的相关问题(stackoverflow.com/questions/2923181)上添加对此答案的引用,我会在此处将您标记为解决方案。这实际上同时解决了这两个问题。
mckamey,2010年

7
感谢您的回答,但是VS2010现在已经包含一个名为“ TargetFrameworkVersion”的新标签,现在对于每个有条件的属性组,仅TargetFrameworkVersion进行了更改,我们是否还需要所有这些使它工作?
Akash Kava 2010年

这个答案不仅是为框架定义了常量,而且还为多个框架进行了构建
katbyte 2013年

4
这篇文章对我有用,但是我不太擅长MSBuild,花了一段时间才弄清楚。我做了一个作为示例的项目。dev6.blob.core.windows.net/blog-images/DualTargetFrameworks.zip
TheDev6 2014年

44

到目前为止,对我来说可行的另一种方法是将以下内容添加到项目文件中:

 <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”(使用新的属性功能)。然后,它删除所有现有的“ NETxx”值,并将其添加到DefinedConstants的末尾。可能可以简化此过程,但是我没有时间摆弄。

在VS中查看项目属性的“生成”选项卡,您将在条件编译符号部分中看到结果值。然后在“应用程序”选项卡上更改目标框架版本,然后自动更改符号。然后,您可以#if NETxx按常规方式使用预处理程序指令。在VS中更改项目似乎不会丢失自定义PropertyGroup。

请注意,这似乎并没有给您“客户端配置文件”目标选项带来任何不同,但这对我来说不是问题。


Jeremy,哇,这太完美了,因为我已经在构建解决方案中单独构建了。
格雷格·芬泽

+1。谁想得到这将是很难找到“$(DefineConstants.Contains(” ......” ??谢谢
CAD小子

我终于找到了进入此页面的方式,因为我需要重新学习如何将这些魔术常数添加到我的构建中。今天,我要重新访问同一项目,以细分库,并且我需要将符号与我一起细分为某些细分。我只是在上面看了一下,发现您的答案已经在原始.CSPROJ文件中适当确认。
David A. Gray

15

这些解决方案有问题,可能是因为我的初始常数是由这些属性预先构建的。

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

我将张贴“高级编译器设置”对话框的屏幕截图(通过单击项目的“编译”选项卡上的“高级编译选项...”按钮打开)。但是,作为新用户,我没有代表这样做。如果可以看到屏幕截图,您将看到属性组自动填充的自定义常量,然后您会说:“我一定要了解其中的一些内容。”


编辑:那个代表出奇地快..谢谢大家!这是该屏幕截图:

高级编译器设置


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>

对“调试”和“发布”构建配置都执行此操作。然后在您的代码中使用:

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

0

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

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

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

.NET框架: NETFRAMEWORKNET20NET35NET40NET45NET451NET452NET46NET461NET462NET47NET471NET472NET48

.NET标准: NETSTANDARDNETSTANDARD1_0NETSTANDARD1_1NETSTANDARD1_2NETSTANDARD1_3NETSTANDARD1_4NETSTANDARD1_5NETSTANDARD1_6NETSTANDARD2_0NETSTANDARD2_1

.NET核心: NETCOREAPPNETCOREAPP1_0NETCOREAPP1_1NETCOREAPP2_0NETCOREAPP2_1NETCOREAPP2_2NETCOREAPP3_0

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.