Arahan praprosesor C#

Meskipun pengompilasi tidak memiliki praprosesor terpisah, arahan yang dijelaskan dalam bagian ini diproses seolah-olah ada satu. Anda menggunakannya untuk membantu dalam kompilasi kondisional. Tidak seperti arahan C dan C ++, Anda tidak dapat menggunakan arahan ini untuk membuat makro. Arahan praprosesor harus menjadi satu-satunya instruksi pada baris.

Konteks yang dapat diubah ke null

Arahan praprosesor #nullable menetapkan konteks anotasi yang dapat diubah ke null dan konteks peringatan yang dapat diubah ke null. Arahan ini mengontrol apakah anotasi yang dapat diubah ke null memiliki efek, dan apakah peringatan nullability diberikan. Setiap konteks entah dinonaktifkan atau diaktifkan.

Kedua konteks dapat ditentukan pada tingkat proyek (di luar kode sumber C#) yang menambahkan Nullable elemen ke PropertyGroup elemen . Arahan #nullable mengontrol anotasi dan konteks peringatan dan lebih diutamakan daripada pengaturan tingkat proyek. Arahan mengatur konteks yang dikendalikannya hingga arahan lain menimpanya, atau hingga akhir file sumber.

Efek dari arahan adalah sebagai berikut:

  • #nullable disable: Mengatur konteks anotasi yang dapat diubah ke null dan peringatan ke dinonaktifkan.
  • #nullable enable: Mengatur konteks anotasi yang dapat diubah ke null dan peringatan ke diaktifkan.
  • #nullable restore: Memulihkan anotasi yang dapat diubah ke null dan konteks peringatan ke pengaturan proyek.
  • #nullable disable annotations: Mengatur konteks anotasi yang dapat diubah ke null ke dinonaktifkan.
  • #nullable enable annotations: Mengatur konteks anotasi yang dapat diubah ke null ke diaktifkan.
  • #nullable restore annotations: Memulihkan konteks anotasi yang dapat diubah ke null ke pengaturan proyek.
  • #nullable disable warnings: Mengatur konteks peringatan yang dapat diubah ke null ke dinonaktifkan.
  • #nullable enable warnings: Mengatur konteks peringatan yang dapat diubah ke null ke diaktifkan.
  • #nullable restore warnings: Memulihkan konteks peringatan yang dapat diubah ke null ke pengaturan proyek.

Kompilasi kondisional

Anda menggunakan empat arahab praprosesor untuk mengontrol kompilasi kondisional:

  • #if: Membuka kompilasi kondisional, di mana kode dikompilasi hanya jika simbol yang ditetapkan telah ditentukan.
  • #elif: Menutup kompilasi kondisional sebelumnya dan membuka kompilasi kondisional baru berdasarkan apakah simbol yang ditetapkan telah ditentukan.
  • #else: Menutup kompilasi kondisional sebelumnya dan membuka kompilasi kondisional baru berdasarkan apakah simbol yang ditetapkan sebelumnya belum ditentukan.
  • #endif: Menutup kompilasi kondisional sebelumnya.

Pengkompilasi C# mengkompilasi kode antara #if direktif dan #endif direktif hanya jika simbol yang ditentukan ditentukan, atau tidak ditentukan ketika ! operator tidak digunakan. Tidak seperti C dan C++, nilai numerik ke simbol tidak dapat ditetapkan. Pernyataan #if dalam C# adalah Boolean dan hanya menguji apakah simbol telah ditentukan atau belum. Misalnya, kode berikut dikompilasi saat DEBUG ditentukan:

#if DEBUG
    Console.WriteLine("Debug version");
#endif

Kode berikut dikompilasi ketika MYTESTtidak ditentukan:

#if !MYTEST
    Console.WriteLine("MYTEST is not defined");
#endif

Anda dapat menggunakan operator == (kesetaraan) dan != (ketidaksetaraan) untuk menguji nilai booltrue atau false. true berarti simbol telah ditentukan. Pernyataan #if DEBUG ini memiliki arti yang sama dengan #if (DEBUG == true). Anda dapat menggunakan operator && (dan), || (atau), dan ! (bukan) untuk mengevaluasi apakah beberapa simbol telah ditentukan. Anda juga dapat mengelompokkan simbol dan operator dengan tanda kurung.

Berikut ini adalah arahan kompleks yang memungkinkan kode Anda untuk memanfaatkan fitur .NET yang lebih baru sambil tetap kompatibel mundur. Misalnya, bayangkan Anda menggunakan paket NuGet dalam kode Anda, tetapi paket hanya mendukung .NET 6 dan yang lebih baru, serta .NET Standard 2.0 dan yang lebih baru:

#if (NET6_0_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
    Console.WriteLine("Using .NET 6+ or .NET Standard 2+ code.");
#elif
    Console.WriteLine("Using older code that doesn't support the above .NET versions.");
#endif

#if, bersama dengan arahan #else, #elif, #endif, #define, dan #undef, memungkinkan Anda menyertakan atau mengecualikan kode berdasarkan keberadaan satu atau beberapa simbol. Kompilasi kondisional dapat berguna saat mengompilasi kode untuk debug build atau saat mengompilasi untuk konfigurasi tertentu.

Arahan kondisional yang dimulai dengan arahan #if harus secara eksplisit dihentikan dengan arahan #endif. #define memungkinkan Anda menentukan simbol. Dengan menggunakan simbol sebagai ekspresi yang diteruskan ke arahan #if, ekspresi mengevaluasi ke true. Anda juga dapat menentukan simbol dengan opsi pengompilasi DefineConstants. Anda dapat membatalkan penentuan simbol dengan #undef. Cakupan simbol yang dibuat dengan #define adalah file tempat simbol ditentukan. Simbol yang Anda tentukan dengan DefineConstants atau dengan #define tidak bertentangan dengan variabel dengan nama yang sama. Artinya, nama variabel tidak boleh diteruskan ke arahan praprosesor, dan simbol hanya dapat dievaluasi oleh arahan praprosesor.

#elif memungkinkan Anda membuat arahan kondisional majemuk. Ekspresi #elif akan dievaluasi jika baik #if sebelumnya maupun ekspresi arahan #elif yang sebelumnya dan opsional tidak dievaluasi ke true. Jika ekspresi #elif mengevaluasi ke true, pengompilasi mengevaluasi semua kode antara #elif dan arahan kondisional berikutnya. Contohnya:

#define VC7
//...
#if DEBUG
    Console.WriteLine("Debug build");
#elif VC7
    Console.WriteLine("Visual Studio 7");
#endif

#else memungkinkan Anda membuat arahan kondisional majemuk, sehingga, jika tidak ada ekspresi dalam arahan #if sebelumnya atau #elif (opsional) yang dievaluasi ke true, pengompilasi akan mengevaluasi semua kode antara #else dan #endif berikutnya. #endif(#endif) harus menjadi arahan praprosesor berikutnya setelah #else.

#endif menentukan akhir dari arahan kondisional, yang dimulai dengan arahan #if.

Sistem build juga mengetahui simbol praprosesor yang telah ditentukan sebelumnya yang mewakili kerangka kerja target yang berbeda dalam proyek bergaya SDK. Simbol tersebut berguna saat membuat aplikasi yang dapat menargetkan lebih dari satu versi .NET.

Kerangka Kerja Target Simbol Simbol tambahan
(tersedia dalam .NET 5+ SDK)
Simbol platform (hanya tersedia
saat Anda menentukan TFM khusus OS)
.NET Framework NETFRAMEWORK, , NET472NET48, NET471, NET47, NET462, NET461, NET46, NET452, NET451, NET45, NET40, , , NET35NET20 NET48_OR_GREATER, , NET472_OR_GREATERNET471_OR_GREATER, NET47_OR_GREATER, NET462_OR_GREATER, NET461_OR_GREATER, NET46_OR_GREATER, NET452_OR_GREATER, NET451_OR_GREATER, NET45_OR_GREATER, , NET40_OR_GREATER, , NET35_OR_GREATERNET20_OR_GREATER
.NET Standar NETSTANDARD, , NETSTANDARD2_1NETSTANDARD2_0, NETSTANDARD1_6, NETSTANDARD1_5, NETSTANDARD1_4, NETSTANDARD1_3, NETSTANDARD1_2, , NETSTANDARD1_1,NETSTANDARD1_0 NETSTANDARD2_1_OR_GREATER, , NETSTANDARD1_6_OR_GREATERNETSTANDARD2_0_OR_GREATER, NETSTANDARD1_5_OR_GREATER, NETSTANDARD1_4_OR_GREATER, NETSTANDARD1_3_OR_GREATER, NETSTANDARD1_2_OR_GREATER, , NETSTANDARD1_1_OR_GREATER,NETSTANDARD1_0_OR_GREATER
.NET 5+ (dan .NET Core) NET, , NET8_0NET7_0, NET6_0, NET5_0, NETCOREAPP, NETCOREAPP3_1, NETCOREAPP3_0, NETCOREAPP2_2, NETCOREAPP2_1, , NETCOREAPP2_0, , NETCOREAPP1_1NETCOREAPP1_0 NET8_0_OR_GREATER, , NET7_0_OR_GREATERNET6_0_OR_GREATER, NET5_0_OR_GREATER, NETCOREAPP3_1_OR_GREATER, NETCOREAPP3_0_OR_GREATER, NETCOREAPP2_2_OR_GREATER, NETCOREAPP2_1_OR_GREATER, NETCOREAPP2_0_OR_GREATER, , , NETCOREAPP1_1_OR_GREATERNETCOREAPP1_0_OR_GREATER ANDROID, , IOSBROWSER, MACCATALYST, MACOS, TVOS, , WINDOWS,
[OS][version] (misalnya IOS15_1),
[OS][version]_OR_GREATER (misalnya IOS15_1_OR_GREATER)

Catatan

  • Simbol tanpa versi ditentukan terlepas dari versi yang Anda targetkan.
  • Simbol khusus versi hanya ditentukan untuk versi yang Anda targetkan.
  • Simbol <framework>_OR_GREATER ditentukan untuk versi yang Anda targetkan dan semua versi sebelumnya. Misalnya, jika Anda menargetkan .NET Framework 2.0, simbol berikut ditentukan: NET20, NET20_OR_GREATER, NET11_OR_GREATER, dan NET10_OR_GREATER.
  • Simbol NETSTANDARD<x>_<y>_OR_GREATER hanya didefinisikan untuk target .NET Standard, dan bukan untuk target yang mengimplementasikan .NET Standard, seperti .NET Core dan .NET Framework.
  • Ini berbeda dari moniker kerangka kerja target (TFM) yang digunakan oleh properti MSBuild TargetFramework dan NuGet.

Catatan

Untuk proyek tradisional bergaya non-SDK, Anda harus mengonfigurasi simbol kompilasi kondisional secara manual untuk kerangka kerja target yang berbeda di Visual Studio melalui halaman properti proyek.

Simbol lain yang telah ditentukan sebelumnya termasuk konstanta DEBUG dan TRACE. Anda dapat menimpa nilai yang ditetapkan untuk proyek menggunakan #define. Simbol DEBUG, misalnya, secara otomatis diatur tergantung pada properti konfigurasi build Anda (mode "Debug" atau "Rilis").

Contoh berikut menunjukkan kepada Anda cara menentukan simbol MYTEST pada file lalu menguji nilai simbol MYTEST dan DEBUG. Output dari contoh ini tergantung pada apakah Anda membangun proyek pada mode konfigurasi Debug atau Rilis.

#define MYTEST
using System;
public class MyClass
{
    static void Main()
    {
#if (DEBUG && !MYTEST)
        Console.WriteLine("DEBUG is defined");
#elif (!DEBUG && MYTEST)
        Console.WriteLine("MYTEST is defined");
#elif (DEBUG && MYTEST)
        Console.WriteLine("DEBUG and MYTEST are defined");
#else
        Console.WriteLine("DEBUG and MYTEST are not defined");
#endif
    }
}

Contoh berikut menunjukkan kepada Anda cara menguji kerangka kerja target yang berbeda sehingga Anda dapat menggunakan API yang lebih baru jika memungkinkan:

public class MyClass
{
    static void Main()
    {
#if NET40
        WebClient _client = new WebClient();
#else
        HttpClient _client = new HttpClient();
#endif
    }
    //...
}

Mendefinisikan simbol

Anda menggunakan dua arahan praprosesor berikut untuk menentukan atau membatalkan penentuan simbol untuk kompilasi kondisional:

  • #define: Tentukan simbol.
  • #undef: Batalkan penentuan simbol.

Anda menggunakan #define untuk menentukan simbol. Saat Anda menggunakan simbol sebagai ekspresi yang diteruskan ke arahan #if, ekspresi akan mengevaluasi ke true, seperti yang ditunjukkan contoh berikut:

#define VERBOSE

#if VERBOSE
   Console.WriteLine("Verbose output version");
#endif

Catatan

Arahan #define tidak dapat digunakan untuk mendeklarasikan nilai konstanta seperti yang biasanya dilakukan dalam C dan C++. Konstanta dalam C # paling baik didefinisikan sebagai anggota statik dari kelas atau struktur. Jika Anda memiliki beberapa konstanta seperti itu, pertimbangkan untuk membuat kelas "Konstanta" terpisah untuk menahannya.

Simbol dapat digunakan untuk menentukan kondisi untuk kompilasi. Anda dapat menguji simbol dengan #if atau #elif. Anda juga dapat menggunakan ConditionalAttribute untuk melakukan kompilasi kondisional. Anda dapat menentukan simbol, tetapi Anda tidak dapat menetapkan nilai ke simbol. Arahan #define harus muncul dalam file sebelum Anda menggunakan instruksi apa pun yang juga bukan arahan praprosesor. Anda juga dapat menentukan simbol dengan opsi pengompilasi DefineConstants. Anda dapat membatalkan penentuan simbol dengan #undef.

Mendefinisikan wilayah

Anda dapat menentukan wilayah kode yang dapat diciutkan dalam kerangka menggunakan dua arahan praprosesor berikut:

  • #region: Mulai wilayah.
  • #endregion: Mengakhiri wilayah.

#region memungkinkan Anda menentukan blok kode yang dapat Anda perluas atau ciutkan saat menggunakan fitur kerangka editor kode. Dalam file kode yang lebih panjang, lebih mudah untuk menciutkan atau menyembunyikan satu atau beberapa wilayah sehingga Anda dapat fokus pada bagian file yang sedang Anda kerjakan. Contoh berikut menunjukkan cara menentukan wilayah:

#region MyClass definition
public class MyClass
{
    static void Main()
    {
    }
}
#endregion

Blok #region harus dihentikan dengan arahan #endregion. Blok #region tidak dapat tumpang tindih dengan blok #if. Namun, blok #region dapat disarangkan dalam blok #if, dan blok #if dapat disarangkan dalam blok #region.

Informasi kesalahan dan peringatan

Anda menginstruksikan pengompilasi untuk menghasilkan kesalahan dan peringatan pengompilasi yang ditentukan pengguna, dan mengontrol informasi baris menggunakan arahan berikut:

  • #error: Hasilkan kesalahan pengompilasi dengan pesan tertentu.
  • #warning: Hasilkan peringatan pengompilasi, dengan pesan spesifik.
  • #line: Ubah nomor baris yang dicetak dengan pesan pengompilasi.

#error memungkinkan Anda menghasilkan kesalahan yang ditentukan pengguna CS1029 dari lokasi tertentu dalam kode Anda. Contoh:

#error Deprecated code in this method.

Catatan

Pengompilasi memperlakukan #error version dengan cara khusus dan melaporkan kesalahan pengompilasi, CS8304, dengan pesan yang berisi pengompilasi dan versi bahasa yang digunakan.

#warning memungkinkan Anda menghasilkan peringatan pengompilasi tingkat satu CS1030 dari lokasi tertentu dalam kode Anda. Contohnya:

#warning Deprecated code in this method.

#line memungkinkan Anda memodifikasi penomoran baris pengompilasi dan (opsional) output nama file untuk kesalahan dan peringatan.

Contoh berikut menunjukkan cara melaporkan dua peringatan yang terkait dengan nomor baris. Arahan #line 200 memaksa nomor baris berikutnya menjadi 200 (meskipun defaultnya adalah #6), dan hingga arahan berikutnya #line, nama file akan dilaporkan sebagai "Spesial". Arahan #line default mengembalikan penomoran baris ke penomoran defaultnya, yang menghitung baris yang dinomori ulang oleh arahan sebelumnya.

class MainClass
{
    static void Main()
    {
#line 200 "Special"
        int i;
        int j;
#line default
        char c;
        float f;
#line hidden // numbering not affected
        string s;
        double d;
    }
}

Kompilasi menghasilkan output berikut:

Special(200,13): warning CS0168: The variable 'i' is declared but never used
Special(201,13): warning CS0168: The variable 'j' is declared but never used
MainClass.cs(9,14): warning CS0168: The variable 'c' is declared but never used
MainClass.cs(10,15): warning CS0168: The variable 'f' is declared but never used
MainClass.cs(12,16): warning CS0168: The variable 's' is declared but never used
MainClass.cs(13,16): warning CS0168: The variable 'd' is declared but never used

Arahan #line dapat digunakan dalam langkah otomatis dan menengah dalam proses build. Misalnya, jika baris dihapus dari file kode sumber asli, tetapi Anda masih ingin pengompilasi menghasilkan output berdasarkan penomoran baris asli dalam file, Anda dapat menghapus baris dan kemudian menyimulasikan penomoran baris asli dengan #line.

Arahan #line hidden menyembunyikan baris berturut-turut dari debugger, sehingga ketika pengembang menelusuri kode, baris apa pun antara #line hidden dan arahan #line berikutnya (dengan asumsi bahwa itu bukan arahan #line hidden lain) akan dilangkahi. Opsi ini juga dapat digunakan untuk memungkinkan ASP.NET membedakan antara kode yang ditentukan pengguna dan yang dibuat mesin. Meskipun ASP.NET adalah konsumen utama dari fitur ini, kemungkinan lebih banyak generator sumber akan menggunakannya.

Arahan #line hidden tidak memengaruhi nama file atau nomor baris dalam pelaporan kesalahan. Artinya, jika pengompilasi menemukan kesalahan dalam blok tersembunyi, pengompilasi akan melaporkan nama file saat ini dan nomor baris kesalahan.

Arahan n#line filename menentukan nama file yang ingin Anda munculkan dalam output pengompilasi. Secara default, nama sebenarnya dari file kode sumber digunakan. Nama file harus dalam tanda kutip ganda ("") dan harus didahului dengan nomor baris.

Dimulai dengan C# 10, Anda dapat menggunakan bentuk baru arahan #line:

#line (1, 1) - (5, 60) 10 "partial-class.cs"
/*34567*/int b = 0;

Komponen dari bentuk ini adalah:

  • (1, 1): Baris dan kolom awal untuk karakter pertama pada baris yang mengikuti arahan. Dalam contoh ini, baris berikutnya akan dilaporkan sebagai baris 1, kolom 1.
  • (5, 60): Baris akhir dan kolom untuk wilayah yang ditandai.
  • 10: Offset kolom agar arahan #line berlaku. Dalam contoh ini, kolom ke-10 akan dilaporkan sebagai kolom satu. Di situlah deklarasi int b = 0; dimulai. Bidang ini bersifat opsional. Jika dihilangkan, arahan berlaku pada kolom pertama.
  • "partial-class.cs": Nama file output.

Contoh sebelumnya akan menghasilkan peringatan berikut:

partial-class.cs(1,5,1,6): warning CS0219: The variable 'b' is assigned but its value is never used

Setelah remapping, variabel, b, berada di baris pertama, pada karakter enam, dari file partial-class.cs.

Bahasa khusus domain (DSL) biasanya menggunakan format ini untuk memberikan pemetaan yang lebih baik dari file sumber ke output C# yang dihasilkan. Penggunaan paling umum dari arahan yang diperluas #line ini adalah memetakan ulang peringatan atau kesalahan yang muncul dalam file yang dihasilkan ke sumber asli. Misalnya, pertimbangkan halaman pisau cukur ini:

@page "/"
Time: @DateTime.NowAndThen

Properti DateTime.Now salah di ketik sebagai DateTime.NowAndThen. C# yang dihasilkan untuk cuplikan pisau cukur ini terlihat seperti berikut ini, di page.g.cs:

  _builder.Add("Time: ");
#line (2, 6) - (2, 27) 15 "page.razor"
  _builder.Add(DateTime.NowAndThen);

Output kompilator untuk cuplikan sebelumnya adalah:

page.razor(2, 2, 2, 27)error CS0117: 'DateTime' does not contain a definition for 'NowAndThen'

Baris 2, kolom 6 di page.razor adalah tempat teks @DateTime.NowAndThen dimulai. Itu dicatat oleh (2, 6) direktif. Rentang @DateTime.NowAndThen itu berakhir pada baris 2, kolom 27. Itu dicatat oleh (2, 27) direktif. Teks untuk DateTime.NowAndThen dimulai di kolom 15 dari page.g.cs. Itu dicatat oleh 15 direktif. Menggabungkan semua argumen, dan pengkompilasi melaporkan kesalahan di lokasinya di page.razor. Pengembang dapat menavigasi langsung ke kesalahan dalam kode sumber mereka, bukan sumber yang dihasilkan.

Untuk melihat contoh lainnya dari format ini, lihat spesifikasi fitur di bagian pada contoh.

Pragma

#pragma memberikan instruksi khusus kepada pengompilasi untuk kompilasi file tempat file muncul. Instruksi harus didukung oleh pengompilasi. Dengan kata lain, Anda tidak dapat menggunakan #pragma untuk membuat instruksi prapemrosesan kustom.

#pragma pragma-name pragma-arguments

Di mana pragma-name adalah nama pragma yang diakui dan pragma-arguments merupakan argumen khusus pragma.

peringatan #pragma

#pragma warning dapat mengaktifkan atau menonaktifkan peringatan tertentu.

#pragma warning disable warning-list
#pragma warning restore warning-list

Di mana warning-list adalah daftar nomor peringatan yang dipisahkan koma. Prefiks "CS" bersifat opsional. Ketika tidak ada nomor peringatan yang ditentukan, disable menonaktifkan semua peringatan dan restore mengaktifkan semua peringatan.

Catatan

Untuk menemukan nomor peringatan di Visual Studio, bangun proyek Anda lalu cari nomor peringatan di jendela Output.

Efek disable mulai pada baris berikutnya dari file sumber. Peringatan dipulihkan pada baris setelah restore. Jika restore tidak ada dalam file, peringatan dipulihkan ke status defaultnya pada baris pertama file apa pun setelahnya dalam kompilasi yang sama.

// pragma_warning.cs
using System;

#pragma warning disable 414, CS3021
[CLSCompliant(false)]
public class C
{
    int i = 1;
    static void Main()
    {
    }
}
#pragma warning restore CS3021
[CLSCompliant(false)]  // CS3021
public class D
{
    int i = 1;
    public static void F()
    {
    }
}

checksum #pragma

Menghasilkan checksum untuk file sumber untuk membantu debugging halaman ASP.NET.

#pragma checksum "filename" "{guid}" "checksum bytes"

Di mana "filename" adalah nama file yang memerlukan pemantauan untuk perubahan atau pembaruan, "{guid}" adalah Pengidentifikasi Unik Global (GUID) untuk algoritma hash, dan "checksum_bytes" merupakan string digit heksadesimal yang mewakili byte checksum. Harus ada jumlah digit heksadesimal yang genap. Jumlah digit ganjil menghasilkan peringatan waktu kompilasi, dan arahan diabaikan.

Debugger Visual Studio menggunakan checksum untuk memastikan bahwa ia selalu menemukan sumber yang tepat. Pengompilasi menghitung checksum untuk file sumber, kemudian memancarkan output ke file database program (PDB). Debugger kemudian menggunakan PDB untuk membandingkan dengan checksum yang dihitung untuk file sumber.

Solusi ini tidak berfungsi untuk proyek ASP.NET, karena checksum yang dihitung adalah untuk file sumber yang dihasilkan, bukan file .aspx. Untuk mengatasi masalah ini, #pragma checksum memberikan dukungan checksum untuk halaman ASP.NET.

Saat Anda membuat proyek ASP.NET di Visual C#, file sumber yang dihasilkan berisi checksum untuk file .aspx, asal sumber dihasilkan. Pengompilasi kemudian menulis informasi ini ke dalam file PDB.

Jika pengompilasi tidak menemukan arahan #pragma checksum dalam file, pengompilasi menghitung checksum dan menulis nilainya ke file PDB.

class TestClass
{
    static int Main()
    {
        #pragma checksum "file.cs" "{406EA660-64CF-4C82-B6F0-42D48172A799}" "ab007f1d23d9" // New checksum
    }
}