Compilazione condizionale e obiettivi del framework


124

Ci sono alcuni posti minori in cui il codice per il mio progetto potrebbe essere migliorato drasticamente se il framework di destinazione fosse una versione più recente. Mi piacerebbe poter sfruttare meglio la compilazione condizionale in C # per cambiarli secondo necessità.

Qualcosa di simile a:

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

Qualcuno di questi simboli viene fornito gratuitamente? Devo inserire questi simboli come parte della configurazione del progetto? Sembra abbastanza facile da fare poiché saprò quale framework viene preso di mira da MSBuild.

/p:DefineConstants="NET40"

Come stanno gestendo le persone questa situazione? Stai creando diverse configurazioni? Stai passando le costanti tramite la riga di comando?



Se desideri una semplice soluzione precotta in VS, per favore vota questa voce utente, visualstudio.uservoice.com/forums/121579-visual-studio/… .
JohnC

1
Dai un'occhiata anche a questo link. Abbastanza esplicativo. blogs.msmvps.com/punitganshani/2015/06/21/…
Marco Alves

gruppi di progetto, ripristino nuget e gruppi ref nuget, bella soluzione: shazwazza.com/post/…
OzBob

Risposte:


119

Uno dei modi migliori per ottenere ciò è creare diverse configurazioni di build nel tuo progetto:

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

E in una delle tue configurazioni predefinite:

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

Che imposterebbe il valore predefinito se non fosse definito altrove. Nel caso precedente, OutputPath ti fornirà un assembly separato ogni volta che crei ogni versione.

Quindi crea un target AfterBuild per compilare le tue diverse versioni:

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

Questo esempio ricompilerà l'intero progetto con la variabile Framework impostata su NET20 dopo la prima compilazione (compilando entrambe e assumendo che la prima build fosse la NET35 predefinita dall'alto). Ogni compilazione avrà i valori di definizione condizionali impostati correttamente.

In questo modo puoi anche escludere alcuni file nel file di progetto se vuoi senza dover #ifdef i file:

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

o anche riferimenti

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

Perfetto. Avevo abbastanza esperienza nell'hackerare il formato msbuild per sapere che poteva essere fatto, ma non abbastanza tempo per capire tutti i dettagli. Grazie mille!
mckamey

Se aggiungi un riferimento a questa risposta nella mia domanda correlata ( stackoverflow.com/questions/2923181 ), ti contrassegnerò come soluzione lì. Questo in realtà risolve entrambi allo stesso tempo.
mckamey

7
Grazie per la risposta, ma ora VS2010 include già un nuovo tag denominato "TargetFrameworkVersion", ora per ogni gruppo di proprietà con condizione, viene modificato solo TargetFrameworkVersion, abbiamo ancora bisogno di tutti questi per farlo funzionare?
Akash Kava

Questa risposta non riguarda solo l'aver definito costanti per il framework, ma anche la creazione di più framework
katbyte

4
Questo post ha funzionato per me, ma non sono bravo con MSBuild e ci è voluto un po 'per capirlo. Ho realizzato un progetto che funziona da esempio. dev6.blob.core.windows.net/blog-images/DualTargetFrameworks.zip
TheDev6

44

Un'alternativa che funziona per me finora è aggiungere quanto segue al file di progetto:

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

Questo assume il valore della proprietà TargetFrameworkVersion, che è come "v3.5", sostituisce "v" e "." per ottenere "NET35" (utilizzando la nuova funzione Property Functions ). Quindi rimuove qualsiasi valore "NETxx" esistente e lo aggiunge alla fine di DefinedConstants. Potrebbe essere possibile semplificarlo, ma non ho il tempo di giocherellare.

Guardando nella scheda Build delle proprietà del progetto in VS vedrai il valore risultante nella sezione dei simboli di compilazione condizionale. La modifica della versione del framework di destinazione nella scheda Applicazione cambia automaticamente il simbolo. È quindi possibile utilizzare #if NETxxle direttive del preprocessore nel solito modo. La modifica del progetto in VS non sembra perdere il PropertyGroup personalizzato.

Tieni presente che questo non sembra darti nulla di diverso per le opzioni di destinazione del profilo del cliente, ma non è un problema per me.


Jeremy, wow, grazie è perfetto dato che sto già costruendo separatamente nella mia soluzione di build.
Greg Finzer

+1. Chi avrebbe mai pensato che sarebbe stato così difficile trovare "$ (DefineConstants.Contains ('..." ?? Grazie
CAD tipo

Finalmente ho trovato di nuovo la strada per questa pagina, perché avevo bisogno di un aggiornamento su come ho ottenuto queste costanti magiche nella mia build. Oggi sto rivisitando lo stesso progetto, per suddividere la libreria, e ho bisogno che i simboli mi accompagnino in alcune delle suddivisioni. Ho appena guardato sopra e ho notato che la tua risposta è già debitamente riconosciuta nel file .CSPROJ originale.
David A. Gray,

15

Ho avuto problemi con queste soluzioni, probabilmente perché le mie costanti iniziali erano pre-costruite da queste proprietà.

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

Visual Studio 2010 ha anche generato un errore a causa dei punti e virgola, sostenendo che sono caratteri illegali. Il messaggio di errore mi ha dato un suggerimento in quanto ho potuto vedere le costanti predefinite separate da virgole, seguite alla fine dal mio punto e virgola "illegale". Dopo un po 'di riformattazione e massaggio sono riuscito a trovare una soluzione che funziona per me.

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

Vorrei pubblicare uno screenshot della finestra di dialogo Impostazioni avanzate del compilatore (aperta facendo clic sul pulsante "Opzioni di compilazione avanzate ..." nella scheda Compila del progetto). Ma come nuovo utente, mi manca il rappresentante per farlo. Se potessi vedere lo screenshot, vedresti le costanti personalizzate compilate automaticamente dal gruppo di proprietà e quindi diresti: "Devo procurarmene un po '".


EDIT: Ottenuto quel rappresentante sorprendentemente veloce .. Grazie ragazzi! Ecco quello screenshot:

Impostazioni avanzate del compilatore


4

Inizia cancellando le costanti:

<PropertyGroup>
  <DefineConstants/>
</PropertyGroup>

Successivamente, crea il tuo debug, traccia e altre costanti come:

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

Infine, crea le costanti del tuo framework:

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

Penso che questo approccio sia molto leggibile e comprensibile.


3

In un file .csproj, dopo una <DefineConstants>DEBUG;TRACE</DefineConstants>riga esistente , aggiungi questo:

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

Eseguire questa operazione sia per le configurazioni di build di debug che di rilascio. Quindi usa nel tuo codice:

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

3
i parametri predefiniti e denominati non sono una funzionalità di .NET framework 4, ma una funzionalità del compilatore .NET 4. Possono essere utilizzati anche in progetti destinati a .NET 2 o .NET 3 a condizione che siano compilati in Visual Studio 2010. È solo zucchero sintattico. D'altra parte, la dinamica è una funzionalità di .NET framework 4 e non è possibile utilizzarla in progetti destinati a framework precedenti.
Thanasis Ioannidis

2

@Azarien, la tua risposta può essere combinata con quella di Jeremy per mantenerla in un unico posto piuttosto che Debug | Release ecc.

Per me, combinare entrambe le varianti funziona meglio, ad esempio includere le condizioni nel codice usando #if NETXX e anche compilare per diverse versioni del framework in una volta sola.

Ho questi nel mio file .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>

e negli obiettivi:

  <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

Se stai usando il sistema di compilazione .NET Core, puoi usare i suoi simboli predefiniti (che in realtà corrispondono già al tuo esempio e non richiedono alcuna modifica al tuo .csproj!):

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

L'elenco dei simboli predefiniti è documentato in Sviluppo di librerie con strumenti multipiattaforma e #if (riferimenti per 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 Nucleo: NETCOREAPP , NETCOREAPP1_0, NETCOREAPP1_1, NETCOREAPP2_0, NETCOREAPP2_1, NETCOREAPP2_2,NETCOREAPP3_0

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.