【问题标题】:Conditional compilation and framework targets条件编译和框架目标
【发布时间】:2011-02-24 18:37:11
【问题描述】:

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

类似:

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

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

/p:DefineConstants="NET40"

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

【问题讨论】:

标签: c# .net-3.5 msbuild .net-4.0


【解决方案1】:

实现此目的的最佳方法之一是在您的项目中创建不同的构建配置:

<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 格式,知道它可以完成,但没有足够的时间弄清楚所有细节。非常感谢!
  • 如果您在我的相关问题 (stackoverflow.com/questions/2923181) 上添加对此答案的引用,我会将您标记为那里的解决方案。这实际上同时解决了这两个问题。
  • 感谢您的回答,但是现在 VS2010 已经包含了一个名为 "TargetFrameworkVersion" 的新标签,现在对于每个有条件的属性组,只更改了 TargetFrameworkVersion,我们还需要所有这些来使其工作吗?
  • 这个答案不仅仅是为框架定义常量,还为多个框架构建
  • 这篇文章对我有用,但我不擅长 MSBuild,花了一段时间才弄明白。我做了一个作为例子的项目。 dev6.blob.core.windows.net/blog-images/DualTargetFrameworks.zip
【解决方案2】:

目前对我有用的另一种方法是将以下内容添加到项目文件中:

 <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,哇,谢谢,这太完美了,因为我已经在我的构建解决方案中单独构建了。
  • +1。谁会想到找到“$(DefineConstants.Contains('...”) 会这么难??谢谢
  • 我终于找到了再次访问此页面的方法,因为我需要重新了解如何将这些魔法常量纳入我的构建。我今天正在重新访问同一个项目,以细分图书馆,我需要符号与我一起进入一些细分。我只是在上面看了看,注意到您的答案已经在原始 .CSPROJ 文件中得到正式承认。
【解决方案3】:

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

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

    我认为这种方法可读性很强。

    【讨论】:

      【解决方案5】:

      在 .csproj 文件中,在现有的 &lt;DefineConstants&gt;DEBUG;TRACE&lt;/DefineConstants&gt; 行之后,添加以下内容:

      <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
      

      【讨论】:

      • 默认和命名参数不是 .NET framework 4 的特性,而是 .NET 4 编译器的特性。只要它们在 Visual Studio 2010 中编译,它们也可以用于面向 .NET 2 或 .NET 3 的项目。它只是语法糖。另一方面,动态是 .NET Framework 4 的一个特性,您不能在之前的针对框架的项目中使用它。
      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

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

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

        预定义符号列表记录在Developing Libraries with Cross Platform Tools#if (C# Reference)

        .NET Framework: NETFRAMEWORK, NET20, NET35, NET40, NET45, NET451, NET452, NET46, NET461, NET462NET47NET471NET472NET48

        .NET 标准: NETSTANDARDNETSTANDARD1_0NETSTANDARD1_1NETSTANDARD1_2NETSTANDARD1_3NETSTANDARD1_4NETSTANDARD1_5NETSTANDARD1_6NETSTANDARD2_0NETSTANDARD2_1

        .NET Core:NETCOREAPPNETCOREAPP1_0NETCOREAPP1_1NETCOREAPP2_0NETCOREAPP2_1NETCOREAPP2_2NETCOREAPP3_0

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-03-03
          • 2020-01-19
          • 2011-03-27
          • 2021-11-02
          • 2011-11-12
          相关资源
          最近更新 更多