Compilación condicional y objetivos marco

124

Hay algunos lugares menores donde el código para mi proyecto podría mejorarse drásticamente si el marco de destino fuera una versión más nueva. Me gustaría poder aprovechar mejor la compilación condicional en C # para cambiarlos según sea necesario.

Algo como:

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

¿Alguno de estos símbolos es gratis? ¿Necesito inyectar estos símbolos como parte de la configuración del proyecto? Parece bastante fácil de hacer, ya que sabré a qué framework se dirige MSBuild.

/p:DefineConstants="NET40"

¿Cómo manejan las personas esta situación? ¿Estás creando diferentes configuraciones? ¿Estás pasando las constantes a través de la línea de comando?

mckamey
fuente
Si desea una solución precocida sencilla en VS, vote por esta voz de usuario, visualstudio.uservoice.com/forums/121579-visual-studio/… .
JohnC
1
Echa un vistazo a este enlace también. Bastante explicativo. blogs.msmvps.com/punitganshani/2015/06/21/…
Marco Alves
grupos de proyectos, restauración de nuget y grupos de referencia de nuget, buena solución: shazwazza.com/post/…
OzBob

Respuestas:

119

Una de las mejores maneras de lograr esto es crear diferentes configuraciones de compilación en su proyecto:

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

Y en una de sus configuraciones predeterminadas:

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

Lo cual establecería el valor predeterminado si no se definiera en ningún otro lugar. En el caso anterior, OutputPath le dará un ensamblaje separado cada vez que cree cada versión.

Luego cree un objetivo AfterBuild para compilar sus diferentes versiones:

<Target Name="AfterBuild">
  <MSBuild Condition=" '$(Framework)' != 'NET20'"
    Projects="$(MSBuildProjectFile)"
    Properties="Framework=NET20"
    RunEachTargetSeparately="true"  />
</Target>

Este ejemplo recompilará todo el proyecto con la variable Framework establecida en NET20 después de la primera compilación (compilando ambas y suponiendo que la primera compilación fue la NET35 predeterminada de arriba). Cada compilación tendrá los valores de definición condicional establecidos correctamente.

De esta manera, incluso puede excluir ciertos archivos en el archivo del proyecto si desea sin tener que #ifdef los archivos:

<Compile Include="SomeNet20SpecificClass.cs" Condition=" '$(Framework)' == 'NET20' " />

o incluso referencias

<Reference Include="Some.Assembly" Condition="" '$(Framework)' == 'NET20' " >
  <HintPath>..\Lib\$(Framework)\Some.Assembly.dll</HintPath>
</Reference>
Todd
fuente
Perfecto. Tenía la experiencia suficiente para hackear el formato msbuild para saber que se podía hacer, pero no el tiempo suficiente para descubrir todos los detalles. ¡Muchas gracias!
mckamey
Si agrega una referencia a esta respuesta en mi pregunta relacionada ( stackoverflow.com/questions/2923181 ), lo marcaré como la solución allí. Esto en realidad los resuelve a ambos al mismo tiempo.
mckamey
77
Gracias por la respuesta, pero ahora VS2010 ya incluye una nueva etiqueta llamada "TargetFrameworkVersion", ahora para cada grupo de propiedades con condición, solo se cambia TargetFrameworkVersion, ¿aún necesitamos todo esto para que funcione?
Akash Kava
Esta respuesta no se trata sólo de tener constantes definidas por el marco, sino también la construcción de múltiples marcos
katbyte
44
Esta publicación funcionó para mí, pero no soy bueno en MSBuild y me llevó un tiempo resolverlo. Hice un proyecto que funciona como un ejemplo. dev6.blob.core.windows.net/blog-images/DualTargetFrameworks.zip
TheDev6
44

Una alternativa que me está funcionando hasta ahora es agregar lo siguiente al archivo del proyecto:

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

Esto toma el valor de la propiedad TargetFrameworkVersion, que es como "v3.5", reemplaza la "v" y "." para obtener "NET35" (utilizando la nueva función de funciones de propiedad ). Luego elimina cualquier valor "NETxx" existente y lo agrega al final de DefinedConstants. Es posible racionalizar esto, pero no tengo tiempo para tocar el violín.

Mirando la pestaña Construir de las propiedades del proyecto en VS, verá el valor resultante en la sección de símbolos de compilación condicional. Al cambiar la versión del marco de destino en la pestaña Aplicación, el símbolo cambia automáticamente. Luego puede usar las #if NETxxdirectivas de preprocesador de la manera habitual. Cambiar el proyecto en VS no parece perder el PropertyGroup personalizado.

Tenga en cuenta que esto no parece ofrecerle algo diferente para las opciones de destino del Perfil del cliente, pero eso no es un problema para mí.

Jeremy Cook
fuente
Jeremy, wow, gracias, esto es perfecto ya que ya estoy construyendo por separado en mi solución de compilación.
Greg Finzer
+1. ¿Quién hubiera pensado que sería tan difícil encontrar "$ (DefineConstants.Contains ('..." ?? Gracias
CAD bloke
Finalmente encontré mi camino a esta página nuevamente, porque necesitaba un repaso sobre cómo conseguí estas constantes mágicas en mi construcción. Hoy estoy revisando el mismo proyecto, para subdividir la biblioteca, y necesito los símbolos para acompañarme en algunas de las subdivisiones. Acabo de mirar por encima y noté que su respuesta ya está debidamente reconocida en el archivo original .CSPROJ.
David A. Gray
15

Tuve problemas con estas soluciones, posiblemente porque mis constantes iniciales fueron preconstruidas por estas propiedades.

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

Visual Studio 2010 también arrojó un error debido a los punto y coma, alegando que son caracteres ilegales. El mensaje de error me dio una pista, ya que pude ver las constantes preconstruidas separadas por comas, finalmente seguidas de mi punto y coma "ilegal". Después de un nuevo formateo y masaje, pude encontrar una solución que me funciona.

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

Publicaría una captura de pantalla del cuadro de diálogo Configuración avanzada del compilador (abierto haciendo clic en el botón "Opciones avanzadas de compilación ..." en la pestaña Compilar de su proyecto). Pero como nuevo usuario, me falta el representante para hacerlo. Si pudieras ver la captura de pantalla, verías las constantes personalizadas rellenadas automáticamente por el grupo de propiedades y luego estarías diciendo: "Tengo que conseguirme algo de eso".


EDITAR: Tengo ese representante sorprendentemente rápido ... ¡Gracias chicos! Aquí está esa captura de pantalla:

Configuración avanzada del compilador

Nathaniel Roark
fuente
4

Comience borrando las constantes:

<PropertyGroup>
  <DefineConstants/>
</PropertyGroup>

Luego, desarrolle su depuración, rastreo y otras constantes como:

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
    <DebugSymbols>true</DebugSymbols>
  <DebugType>full</DebugType>
  <Optimize>false</Optimize>
  <DefineConstants>TRACE;DEBUG;$(DefineConstants)</DefineConstants>
</PropertyGroup>

Por último, construya sus constantes marco:

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

Creo que este enfoque es muy legible y comprensible.

zDougie
fuente
3

En un archivo .csproj, después de una <DefineConstants>DEBUG;TRACE</DefineConstants>línea existente , agregue esto:

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

Haga esto para las configuraciones de compilación Debug y Release. Luego use en su código:

#if NET_40_OR_GREATER
   // can use dynamic, default and named parameters
#endif
Azarien
fuente
3
los parámetros predeterminados y con nombre no son una característica de .NET framework 4, sino una característica del compilador .NET 4. También se pueden usar en proyectos dirigidos a .NET 2 o .NET 3 siempre que se compilen en Visual Studio 2010. Es solo azúcar sintáctico. Por otro lado, dinámico es una característica de .NET Framework 4, y no puede usarlo en proyectos de marcos de destino antes de esto.
Thanasis Ioannidis
2

@Azarien, tu respuesta se puede combinar con la de Jeremy para mantenerla en un solo lugar en lugar de Debug | Release, etc.

Para mí, combinar ambas variaciones funciona mejor, es decir, incluir condiciones en el código usando #if NETXX y también compilar para diferentes versiones de marco de una vez.

Tengo estos en mi archivo .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>

y en objetivos:

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

Si está utilizando el sistema de compilación .NET Core, puede usar sus símbolos predefinidos (que ya coinciden con su ejemplo y no requieren ningún cambio en su .csproj):

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

La lista de símbolos predefinidos se documenta en Desarrollo de bibliotecas con herramientas multiplataforma y #if (Referencia de 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

Kevinoid
fuente