Kompilasi bersyarat dan target kerangka kerja

124

Ada beberapa tempat kecil di mana kode untuk proyek saya mungkin dapat ditingkatkan secara drastis jika kerangka kerja target adalah versi yang lebih baru. Saya ingin dapat memanfaatkan kompilasi bersyarat dengan lebih baik di C # untuk mengubahnya sesuai kebutuhan.

Sesuatu seperti:

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

Apakah salah satu simbol ini datang secara gratis? Apakah saya perlu memasukkan simbol ini sebagai bagian dari konfigurasi proyek? Tampaknya cukup mudah dilakukan karena saya akan tahu kerangka mana yang menjadi target dari MSBuild.

/p:DefineConstants="NET40"

Bagaimana orang menangani situasi ini? Apakah Anda membuat konfigurasi yang berbeda? Apakah Anda meneruskan konstanta melalui baris perintah?

mckamey
sumber
Jika Anda menginginkan solusi pra-panggang sederhana di VS, silakan pilih suara pengguna ini, visualstudio.uservoice.com/forums/121579-visual-studio/… .
JohnC
1
Lihatlah tautan ini juga. Cukup jelas. blogs.msmvps.com/punitganshani/2015/06/21/…
Marco Alves
grup proyek, pemulihan nuget, dan grup ref nuget, solusi bagus: shazwazza.com/post/…
OzBob

Jawaban:

119

Salah satu cara terbaik untuk melakukannya adalah dengan membuat konfigurasi build yang berbeda dalam proyek Anda:

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

Dan di salah satu konfigurasi default Anda:

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

Yang akan menyetel default jika tidak ditentukan di tempat lain. Dalam kasus di atas, OutputPath akan memberi Anda perakitan terpisah setiap kali Anda membangun setiap versi.

Kemudian buat target AfterBuild untuk mengompilasi berbagai versi Anda:

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

Contoh ini akan mengkompilasi ulang seluruh proyek dengan variabel Framework disetel ke NET20 setelah build pertama (mengompilasi keduanya dan mengasumsikan bahwa build pertama adalah NET35 default dari atas). Setiap kompilasi akan memiliki nilai definisi bersyarat yang disetel dengan benar.

Dengan cara ini Anda bahkan dapat mengecualikan file tertentu dalam file proyek jika Anda ingin #ifdef file:

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

atau bahkan referensi

<Reference Include="Some.Assembly" Condition="" '$(Framework)' == 'NET20' " >
  <HintPath>..\Lib\$(Framework)\Some.Assembly.dll</HintPath>
</Reference>
Todd
sumber
Sempurna. Saya hanya punya cukup pengalaman meretas format msbuild untuk mengetahui itu bisa dilakukan, tetapi tidak cukup waktu untuk mengetahui semua detailnya. Terima kasih banyak!
mckamey
Jika Anda menambahkan referensi ke jawaban ini pada pertanyaan terkait saya ( stackoverflow.com/questions/2923181 ), saya akan menandai Anda sebagai solusi di sana. Ini sebenarnya menyelesaikan keduanya pada saat yang bersamaan.
mckamey
7
Thanks for answer, tapi sekarang VS2010 sudah menyertakan tag baru bernama "TargetFrameworkVersion", sekarang untuk setiap grup properti dengan kondisi, hanya TargetFrameworkVersion yang diubah, apakah kita masih perlu semua ini untuk membuatnya berfungsi?
Akash Kava
Jawaban ini tidak hanya tentang menetapkan konstanta untuk kerangka kerja tetapi juga membangun untuk beberapa kerangka kerja
katbyte
4
Posting ini berhasil untuk saya tetapi saya tidak pandai MSBuild dan butuh beberapa saat untuk mengetahuinya. Saya membuat proyek yang berfungsi sebagai contoh. dev6.blob.core.windows.net/blog-images/DualTargetFrameworks.zip
TheDev6
44

Alternatif yang berhasil untuk saya sejauh ini adalah menambahkan yang berikut ini ke file proyek:

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

Ini mengambil nilai properti TargetFrameworkVersion, seperti "v3.5", menggantikan "v" dan "." untuk mendapatkan "NET35" (menggunakan fitur Fungsi Properti baru ). Ini kemudian menghapus nilai "NETxx" yang ada dan menambahkannya ke akhir DefinedConstants. Mungkin saja untuk merampingkan ini, tetapi saya tidak punya waktu untuk bermain-main.

Melihat pada tab Build dari properti proyek di VS Anda akan melihat nilai yang dihasilkan di bagian simbol kompilasi bersyarat. Mengubah versi kerangka kerja target pada tab Aplikasi kemudian mengubah simbol secara otomatis. Anda kemudian dapat menggunakan #if NETxxarahan preprocessor dengan cara biasa. Mengubah proyek di VS tampaknya tidak menghilangkan PropertyGroup kustom.

Perhatikan bahwa ini tampaknya tidak memberi Anda sesuatu yang berbeda untuk opsi target Profil Klien, tetapi itu bukan masalah bagi saya.

Jeremy Cook
sumber
Jeremy, wow terima kasih ini sempurna karena saya sudah membangun secara terpisah dalam solusi build saya.
Greg Finzer
+1. Siapa yang mengira akan sangat sulit untuk menemukan "$ (DefineConstants.Contains ('..." ?? Terima kasih
CAD cowok
Saya akhirnya menemukan jalan saya ke halaman ini lagi, karena saya membutuhkan penyegaran tentang bagaimana saya mendapatkan konstanta ajaib ini ke dalam bangunan saya. Saya hari ini meninjau kembali proyek yang sama, untuk membagi perpustakaan, dan saya membutuhkan simbol untuk menyertai saya menjadi beberapa subdivisi. Saya baru saja melihat di atasnya, dan melihat bahwa jawaban Anda sudah diakui dalam file .CSPROJ asli.
David A. Gray
15

Saya mengalami masalah dengan solusi ini, mungkin karena konstanta awal saya dibuat sebelumnya oleh properti ini.

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

Visual Studio 2010 juga memunculkan kesalahan karena titik koma, mengklaim bahwa mereka adalah karakter ilegal. Pesan kesalahan memberi saya petunjuk karena saya dapat melihat konstanta yang dibuat sebelumnya dipisahkan oleh koma, yang akhirnya diikuti oleh titik koma "ilegal" saya. Setelah beberapa kali memformat ulang dan memijat saya dapat menemukan solusi yang sesuai untuk saya.

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

Saya akan memposting tangkapan layar dari dialog Pengaturan Kompilator Lanjutan (dibuka dengan mengklik tombol "Opsi Kompilasi Lanjutan ..." pada tab Kompilasi proyek Anda). Tetapi sebagai pengguna baru, saya tidak memiliki perwakilan untuk melakukannya. Jika Anda dapat melihat tangkapan layar, Anda akan melihat konstanta khusus diisi secara otomatis oleh grup properti dan kemudian Anda akan berkata, "Saya harus memberi saya beberapa dari itu."


EDIT: Mendapat rep yang sangat cepat .. Terima kasih teman-teman! Ini tangkapan layarnya:

Pengaturan Kompilator Lanjutan

Nathaniel Roark
sumber
4

Mulailah dengan membersihkan konstanta:

<PropertyGroup>
  <DefineConstants/>
</PropertyGroup>

Selanjutnya, buat debug, pelacakan, dan konstanta lainnya seperti:

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

Terakhir, buat konstanta framework Anda:

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

Saya pikir pendekatan ini sangat mudah dibaca dan dimengerti.

zDougie
sumber
3

Dalam file .csproj, setelah <DefineConstants>DEBUG;TRACE</DefineConstants>baris yang ada , tambahkan ini:

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

Lakukan ini untuk konfigurasi build Debug dan Rilis. Kemudian gunakan dalam kode Anda:

#if NET_40_OR_GREATER
   // can use dynamic, default and named parameters
#endif
Azarien
sumber
3
Parameter default dan bernama bukan fitur .NET framework 4, tetapi fitur compiler .NET 4. Mereka dapat digunakan juga dalam proyek yang menargetkan .NET 2 atau .NET 3 selama mereka dikompilasi dalam Visual Studio 2010. Itu hanya gula sintaksis. Di sisi lain, dinamis adalah fitur .NET framework 4, dan Anda tidak dapat menggunakannya dalam kerangka kerja penargetan proyek sebelumnya.
Thanasis Ioannidis
2

@Azarien, jawaban Anda dapat digabungkan dengan jawaban Jeremy untuk menyimpannya di satu tempat daripada Debug | Rilis dll.

Bagi saya, menggabungkan kedua variasi bekerja paling baik yaitu menyertakan kondisi dalam kode menggunakan #if NETXX dan juga membangun versi kerangka kerja yang berbeda sekaligus.

Saya memiliki ini di file .csproj saya:

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

dan dalam target:

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

Jika Anda menggunakan sistem build .NET Core, Anda dapat menggunakan simbol yang ditentukan sebelumnya (yang sebenarnya sudah cocok dengan contoh Anda dan tidak memerlukan perubahan apa pun pada Anda .csproj!):

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

Daftar simbol yang telah ditentukan sebelumnya didokumentasikan di Mengembangkan Perpustakaan dengan Alat Lintas Platform dan #if (Referensi 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 Inti: NETCOREAPP , NETCOREAPP1_0, NETCOREAPP1_1, NETCOREAPP2_0, NETCOREAPP2_1, NETCOREAPP2_2,NETCOREAPP3_0

Kevinoid
sumber