Menu

Pattern Matching di C#: is, Pola switch, Pola Property dan List

Cara kerja pattern matching di C#: operator is dengan pola tipe dan pola konstanta, pernyataan switch dengan pola di case dan when, ekspresi switch, pola property, tuple, dan posisional, pola relasional dan logika (and, or, not), pola list, serta versi C# yang menambahkan masing-masing.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Pattern matching menguji sebuah nilai terhadap suatu bentuk: "apakah ini Circle?", "apakah ini null?", "apakah Status-nya "paid" dan Total-nya di atas 100?". Jika tesnya berhasil, pola juga bisa mengambil bagian dari nilai itu ke dalam variabel baru. Fitur ini dimulai kecil di C# 7 dengan operator is dan terus berkembang di setiap versi sejak itu, jadi halaman ini mencatat versi untuk setiap bentuknya.

Pola tipe dengan is (C# 7)

Sebelum C# 7, memeriksa tipe lalu memakainya butuh dua langkah: as plus cek null, atau is plus cast. Pola tipe melakukan keduanya sekaligus dan memberimu variabel yang sudah bertipe:

Output:

card ending 4242: 49.90
transfer from PT50...: 1200
no payment
other payment: 5

p is CardPayment card bernilai true hanya jika p adalah CardPayment yang tidak null (atau class turunannya), dan saat itu card menyimpannya dengan tipe statis yang tepat. Nilai null tidak pernah cocok dengan pola tipe, itulah sebabnya pembayaran null lolos sampai ke cek null.

Cakupan dan penetapan pasti

Variabel pola ada di blok yang melingkupinya, tetapi compiler hanya mengizinkanmu membacanya di tempat yang pencocokannya pasti berhasil. Ini membuat bentuk "guard clause" bisa dipakai: kamu keluar lebih awal, lalu memakai variabelnya sesudahnya:

Output:

19.99
42 is over 40
price must be text

boxed is int n && n > 40 menunjukkan bentuk umum lainnya: variabel bisa dipakai di sisi kanan &&, karena sisi itu hanya berjalan jika pencocokannya berhasil. Di sisi kanan || variabel itu belum diberi nilai, dan compiler akan memberitahumu (CS0165).

Pola juga melakukan unboxing: boxed is int n berhasil untuk int yang sudah dibungkus (boxed) dan memberimu nilainya tanpa cast yang bisa melempar exception. Untuk nullable value type, maybe is int n berhasil tepat saat maybe punya nilai.

Pola konstanta dan pola null

Konstanta juga merupakan pola. x is null adalah yang paling sering dipakai, dan C# 7 juga mengizinkan x is 0, status is "paid", atau anggota enum:

if (customer is null) return;          // null check that ignores any == overload
if (count is 0) Console.WriteLine("empty");
if (level is LogLevel.Error) Alert();

Banyak tim lebih memilih is null daripada == null karena hasilnya tidak bisa dialihkan oleh overload operator pada tipenya. C# 9 menambahkan bentuk negasinya, is not null.

Pola di pernyataan switch (C# 7)

Pola tipe yang sama bisa menjadi label case, dan klausa when menambahkan kondisi. Case dicoba dari atas ke bawah, jadi urutkan dari yang spesifik ke yang umum:

static string Describe(object o)
{
    switch (o)
    {
        case null:                           return "nothing";
        case int n when n < 0:               return "negative number";
        case int n:                          return $"number {n}";
        case string s when s.Length == 0:    return "empty text";
        case string s:                       return $"text of {s.Length} chars";
        case IEnumerable<int> list:          return "a sequence of numbers";
        default:                             return o.GetType().Name;
    }
}

Describe(-4);                 // negative number
Describe("");                 // empty text
Describe(new List<int>());    // a sequence of numbers
Describe(2.5);                // Double

Compiler memeriksa urutannya: case yang tidak akan pernah cocok karena sudah dicakup case sebelumnya adalah error CS8120. default selalu dievaluasi terakhir, di mana pun letaknya. Logika yang sama yang ditulis dengan rangkaian if/is, seperti contoh pembayaran di atas, berjalan di semua versi C# mulai dari 7.

Ekspresi switch (C# 8)

Ekspresi switch adalah bentuk ringkas untuk "hitung satu nilai dari beberapa case". Nilainya ditulis lebih dulu, lalu switch, lalu cabang-cabang pattern => result yang dipisahkan koma, dengan _ sebagai penangkap semua:

public abstract record Shape;
public record Circle(double Radius) : Shape;
public record Rectangle(double Width, double Height) : Shape;

static double Area(Shape shape) => shape switch
{
    Circle c => Math.PI * c.Radius * c.Radius,
    Rectangle { Width: var w, Height: var h } => w * h,
    _ => throw new ArgumentException("Unknown shape", nameof(shape)),
};

Area(new Rectangle(3, 4));   // 12

Jika tidak ada cabang yang cocok saat program berjalan, ekspresi ini melempar SwitchExpressionException, dan compiler memberi peringatan (CS8509) jika ia bisa melihat ada input yang tidak ditangani. Halaman ekspresi switch membahas sintaksnya secara khusus; sisa halaman ini membahas pola yang bisa kamu taruh di cabang-cabangnya.

Pola property (C# 8, diperluas di C# 10)

Pola property mencocokkan property sebuah object dengan pola bersarang, di dalam kurung kurawal. Object itu juga harus tidak null, jadi { } saja berarti "tidak null":

public record Address(string City, string Country);
public record Order(decimal Total, string Status, Address ShipTo, int Items);

static string Shipping(Order order) => order switch
{
    { Status: "cancelled" }                   => "no shipment",
    { ShipTo.Country: "PT", Total: >= 50m }   => "free, domestic",     // C# 10 dotted form
    { ShipTo.Country: "PT" }                  => "4.90, domestic",
    { Total: > 200m }                         => "free, international",
    _                                         => "12.00, international",
};

Shipping(new Order(60m, "paid", new Address("Porto", "PT"), 2));    // free, domestic
Shipping(new Order(260m, "paid", new Address("Lyon", "FR"), 2));    // free, international

Di C# 8 dan 9, bentuk bersarang wajib dipakai: { ShipTo: { Country: "PT" } }. C# 10 mengizinkan singkatan bertitik seperti di atas. Pola property juga bisa dipakai dengan is, sehingga pengecekan dengan banyak kondisi terbaca seperti sebuah deskripsi: if (order is { Status: "paid", Items: > 0 }).

Pola relasional dan logika (C# 9)

C# 9 menambahkan perbandingan (<, <=, >, >=) dan penggabung and, or, dan not, yang mengubah rentang nilai menjadi pola:

static string Grade(int score) => score switch
{
    < 0 or > 100      => "invalid",
    >= 90             => "A",
    >= 75 and < 90    => "B",
    >= 50             => "C",
    _                 => "F",
};

Grade(82);    // B
Grade(101);   // invalid

if (input is not null and not "") { ... }
if (c is >= 'a' and <= 'z' or >= 'A' and <= 'Z') { ... }   // and binds tighter than or

Padanannya di C# 7 adalah rangkaian if/else if atau switch dengan klausa when (case int s when s >= 90:).

Pola tuple dan posisional (C# 8)

Switch pada beberapa nilai sekaligus dilakukan dengan switch pada sebuah tuple:

static string Quadrant(int x, int y) => (x, y) switch
{
    (0, 0)       => "origin",
    (> 0, > 0)   => "I",
    (< 0, > 0)   => "II",
    (< 0, < 0)   => "III",
    (> 0, < 0)   => "IV",
    _            => "on an axis",
};

Quadrant(-2, 5);   // II

Pola posisional melakukan hal yang sama untuk tipe apa pun yang punya method Deconstruct, termasuk record: case Point(0, 0): atau p is Rectangle(var w, var h) && w == h.

Pola list (C# 11)

Pola list mencocokkan array dan list berdasarkan elemennya. .. cocok dengan elemen berapa pun jumlahnya (sebuah "slice"), dan setiap posisi elemen bisa berisi pola lain apa pun:

static string Route(string[] args) => args switch
{
    []                         => "help",
    ["add", var item]          => $"add {item}",
    ["remove", var item, ..]   => $"remove {item}",
    [var cmd, ..]              => $"unknown command {cmd}",
};

Route(new[] { "add", "milk" });             // add milk
Route(new[] { "remove", "milk", "now" });   // remove milk

int[] nums = { 1, 2, 3, 4 };
if (nums is [1, .., var last]) Console.WriteLine(last);   // 4

Versi yang menambahkan apa

PolaContohVersi
Pola tipe dengan variabelo is Circle cC# 7.0
Pola konstantax is null, n is 0C# 7.0
Pola varx is var vC# 7.0
Pola di label case, guard whencase int n when n < 0:C# 7.0
Ekspresi switch, discard _x switch { ... }C# 8
Pola property{ Status: "paid" }C# 8
Pola tuple(x, y) switch { (0, 0) => ... }C# 8
Pola posisionalPoint(0, 0)C# 8
Pola relasional> 100, <= 0C# 9
Pola logikanot null, >= 1 and <= 5, 'a' or 'b'C# 9
Pola tipe tanpa variabelcase Circle:, Circle => ...C# 9
Pola property yang diperluas{ ShipTo.City: "Porto" }C# 10
Pola list dan slice[first, .., last]C# 11

Kesalahan umum

  • Mengurutkan case dari yang umum ke yang spesifik. case Payment p: sebelum case CardPayment c: membuat case kedua tidak bisa tercapai (CS8120). Yang spesifik dulu.
  • Mengira pola tipe cocok dengan null. null is string s bernilai false. Tangani null dengan case tersendiri atau dengan is null.
  • Membaca variabel pola di tempat pencocokannya mungkin gagal. Setelah a is int n || ..., n belum diberi nilai (CS0165).
  • Switch tipe yang panjang alih-alih virtual method. Jika setiap subclass baru berarti mengedit switch yang sama di lima tempat, perilaku itu seharusnya ada di hierarki class. Pola paling berguna untuk data dari luar (bentuk JSON, pesan, tuple input) dan untuk kumpulan tipe tertutup yang kamu kendalikan.
  • Lupa cabang penangkap semua di ekspresi switch. Input yang tidak cocok akan melempar exception saat program berjalan; perhatikan peringatan CS8509.

Pertanyaan yang sering diajukan

Apa itu pattern matching di C#?

Pattern matching menguji sebuah nilai terhadap suatu bentuk, dan jika cocok, bisa langsung mengambil bagiannya ke dalam variabel dalam langkah yang sama. if (shape is Circle c) menguji tipenya dan memberimu c yang sudah bertipe; order is { Status: "paid", Total: > 100 } menguji property-nya. Pola muncul di ekspresi is, pernyataan switch, dan ekspresi switch.

Bagaimana cara memakai is dengan variabel di C#?

if (obj is Customer c) { ... } memeriksa bahwa obj adalah Customer (bukan null) lalu memasukkannya ke c, dengan tipe Customer, di dalam if. Cara ini menggantikan pasangan lama var c = obj as Customer; if (c != null). Variabel itu pasti terisi hanya di tempat yang tesnya pasti bernilai true, jadi if (!(obj is Customer c)) return; membuat c tetap bisa dipakai setelah if.

Apa perbedaan is null dan == null di C#?

x is null selalu memeriksa referensinya sendiri. x == null memanggil operator == milik tipe itu jika tipe tersebut punya overload untuk operator itu, yang biasanya memberi jawaban yang sama tetapi tidak dijamin. Untuk kebalikannya, tulis x != null, !(x is null), atau x is not null mulai C# 9.

Bagaimana switch dengan pattern matching dan when bekerja di C#?

Label case bisa berisi pola plus guard when: case Order o when o.Total > 100:. Case itu cocok hanya jika polanya cocok dan kondisinya true, dan case dicoba dari atas ke bawah, jadi taruh yang lebih spesifik lebih dulu. Compiler melaporkan CS8120 jika sebuah case tidak akan pernah tercapai karena case sebelumnya sudah mencakupnya.

Versi C# mana yang menambahkan pola apa?

C# 7.0: pola tipe, konstanta, dan var di is dan case, plus when. C# 8: ekspresi switch, pola property, tuple, dan posisional. C# 9: pola relasional (> 5), logika (and, or, not), dan pola tipe tanpa variabel. C# 10: pola property yang diperluas ({ Address.City: "Porto" }). C# 11: pola list ([1, .., var last]).

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai