Pencocokan pola dalam C # 7

C # 7 akhirnya memiliki fitur yang lama dinanti yang disebut Pencocokan Pola. Jika Anda terbiasa dengan bahasa fungsional seperti F #, maka fungsi ini dalam bentuk yang ada saat ini mungkin sedikit mengecewakan Anda. Tetapi bahkan hari ini, dapat menyederhanakan kode dalam berbagai kasus. Lebih detail di bawah cut!



Setiap fitur baru dapat berbahaya bagi pengembang yang membuat aplikasi yang kinerjanya sangat penting. Level abstraksi baru itu bagus, tetapi untuk menggunakannya secara efektif, Anda perlu memahami cara kerjanya. Artikel ini membahas fungsi pencocokan pola dan cara kerjanya.

Sampel dalam C # dapat digunakan dalam ekspresi is, serta dalam blok kasus dari pernyataan switch.
Ada tiga jenis sampel:

  • sampel konstan;
  • jenis sampel;
  • variabel sampel.

Pencocokan pola dalam ekspresi


public void IsExpressions(object o) { // Alternative way checking for null if (o is null) Console.WriteLine("o is null"); // Const pattern can refer to a constant value const double value = double.NaN; if (o is value) Console.WriteLine("o is value"); // Const pattern can use a string literal if (o is "o") Console.WriteLine("o is \"o\""); // Type pattern if (o is int n) Console.WriteLine(n); // Type pattern and compound expressions if (o is string s && s.Trim() != string.Empty) Console.WriteLine("o is not blank"); } 

Menggunakan ekspresi is, Anda dapat memeriksa apakah nilainya konstan, dan dengan menggunakan tipe check Anda juga dapat menentukan variabel sampel.

Saat menggunakan pencocokan pola dalam ekspresi, Anda harus memperhatikan beberapa poin menarik:

  • Variabel yang dimasukkan oleh pernyataan if dikirim ke lingkup luar.
  • Variabel yang dimasukkan oleh pernyataan if ditetapkan secara eksplisit hanya ketika pola cocok.
  • Implementasi pencocokan pola saat ini dalam ekspresi tidak terlalu efisien.

Pertama, pertimbangkan dua kasus pertama:

 public void ScopeAndDefiniteAssigning(object o) { if (o is string s && s.Length != 0) { Console.WriteLine("o is not empty string"); } // Can't use 's' any more. 's' is already declared in the current scope. if (o is int n || (o is string s2 && int.TryParse(s2, out n))) { Console.WriteLine(n); } } 

Pernyataan if pertama memperkenalkan variabel s, terlihat di dalam keseluruhan metode. Ini masuk akal, tetapi mempersulit logika jika pernyataan if lainnya di blok yang sama mencoba untuk menggunakan kembali nama yang sama. Dalam hal ini, pastikan untuk menggunakan nama yang berbeda untuk menghindari konflik.

Variabel yang dimasukkan dalam ekspresi is diberikan secara eksplisit hanya ketika predikat benar. Ini berarti bahwa variabel n dalam pernyataan kedua jika tidak ditetapkan dalam operan yang tepat, tetapi karena sudah dinyatakan, kita dapat menggunakannya sebagai variabel keluar dalam metode int.TryParse.

Poin ketiga yang disebutkan di atas adalah yang paling penting. Perhatikan contoh berikut:

 public void BoxTwice(int n) { if (n is 42) Console.WriteLine("n is 42"); } 

Dalam kebanyakan kasus, ekspresi dikonversikan ke objek. Setara (konstan, variabel) [walaupun karakteristiknya mengatakan bahwa operator == harus digunakan untuk tipe sederhana]:

 public void BoxTwice(int n) { if (object.Equals(42, n)) { Console.WriteLine("n is 42"); } } 

Kode ini meminta dua proses konversi kemasan yang dapat secara signifikan mempengaruhi kinerja jika digunakan pada jalur aplikasi yang kritis. Sebelumnya, o adalah ekspresi nol yang disebut kemasan jika variabel o adalah tipe nullable (lihat kode Suboptimal untuk e adalah nol ), tetapi ada harapan bahwa ini akan diperbaiki (berikut adalah permintaan yang sesuai pada github ).

Jika variabel n adalah tipe objek, maka ekspresi o adalah 42 akan menyebabkan satu "pengemasan-konversi" proses (untuk literal 42), meskipun kode yang sama berdasarkan pernyataan switch tidak akan mengarah ke ini.

Variabel sampel dalam adalah ekspresi


Pola variabel adalah jenis jenis pola khusus dengan satu perbedaan besar: pola akan cocok dengan nilai apa pun, bahkan nol.

 public void IsVar(object o) { if (o is var x) Console.WriteLine($"x: {x}"); } 

Ekspresi o adalah objek akan benar jika o tidak nol, tetapi ekspresi o adalah var x akan selalu benar. Oleh karena itu, kompiler dalam mode rilis * sepenuhnya mengecualikan pernyataan if dan meninggalkan panggilan metode Konsol. Sayangnya, kompiler tidak memperingatkan tentang tidak tersedianya kode dalam kasus berikut: if (! (O var x)) Console.WriteLine ("Unreachable"). Ada harapan bahwa ini juga akan diperbaiki.

* Tidak jelas mengapa perilaku ini hanya berbeda dalam mode rilis. Tampaknya akar semua masalah adalah sama: implementasi awal fungsi tidak optimal. Namun, dilihat dari komentar ini oleh Neal Gafter, semuanya akan segera berubah: β€œKode untuk mencocokkan dengan sampel akan ditulis ulang dari awal (untuk juga mendukung sampel rekursif). Saya pikir sebagian besar perbaikan yang Anda bicarakan akan diimplementasikan dalam kode baru dan tersedia secara gratis. Namun, ini akan memakan waktu. "

Tidak adanya pemeriksaan nol membuat situasi ini istimewa dan berpotensi berbahaya. Namun, jika Anda tahu persis cara kerja sampel ini, maka itu bisa bermanfaat. Ini dapat digunakan untuk memperkenalkan variabel sementara ke dalam ekspresi:

 public void VarPattern(IEnumerable<string> s) { if (s.FirstOrDefault(o => o != null) is var v && int.TryParse(v, out var n)) { Console.WriteLine(n); } } 

Apakah ungkapan dan pernyataan Elvis


Ada kasus lain yang mungkin terbukti bermanfaat. Jenis sampel hanya cocok dengan nilai saat bukan nol. Kita dapat menggunakan logika "filtering" ini dengan operator terdistribusi nol untuk membuat kode lebih mudah dibaca:

 public void WithNullPropagation(IEnumerable<string> s) { if (s?.FirstOrDefault(str => str.Length > 10)?.Length is int length) { Console.WriteLine(length); } // Similar to if (s?.FirstOrDefault(str => str.Length > 10)?.Length is var length2 && length2 != null) { Console.WriteLine(length2); } // And similar to var length3 = s?.FirstOrDefault(str => str.Length > 10)?.Length; if (length3 != null) { Console.WriteLine(length3); } } 

Perhatikan bahwa pola yang sama dapat digunakan untuk tipe nilai dan tipe referensi.

Pencocokan pola dalam blok kasus


Fungsionalitas pernyataan switch telah diperluas dalam C # 7 sehingga pola sekarang dapat digunakan dalam klausa kasus:

 public static int Count<T>(this IEnumerable<T> e) { switch (e) { case ICollection<T> c: return c.Count; case IReadOnlyCollection<T> c: return c.Count; // Matches concurrent collections case IProducerConsumerCollection<T> pc: return pc.Count; // Matches if e is not null case IEnumerable<T> _: return e.Count(); // Default case is handled when e is null default: return 0; } } 

Contoh ini menunjukkan set pertama perubahan pada pernyataan switch.

  1. Variabel jenis apa pun dapat digunakan dengan pernyataan sakelar.
  2. Klausa kasus memungkinkan Anda untuk menentukan pola.
  3. Urutan klausa kasus itu penting. Kompiler akan melempar kesalahan jika kalimat sebelumnya sesuai dengan tipe dasar, dan di sebelah yang diturunkan.
  4. Penawaran khusus diperiksa secara implisit untuk null **. Dalam contoh di atas, klausa kasus terakhir valid karena hanya cocok ketika argumen tidak nol.

** Kalimat terakhir dari kasus ini menunjukkan fungsi lain yang ditambahkan dalam C # 7 - sampel variabel kosong. Nama khusus _ memberi tahu kompiler bahwa variabel tidak diperlukan. Jenis sampel dalam klausa kasus membutuhkan alias. Tetapi jika Anda tidak membutuhkannya, Anda bisa menggunakan _.

Cuplikan berikut menunjukkan fitur lain dari pencocokan pola berdasarkan pernyataan sakelar - kemampuan untuk menggunakan predikat:

 public static void FizzBuzz(object o) { switch (o) { case string s when s.Contains("Fizz") || s.Contains("Buzz"): Console.WriteLine(s); break; case int n when n % 5 == 0 && n % 3 == 0: Console.WriteLine("FizzBuzz"); break; case int n when n % 5 == 0: Console.WriteLine("Fizz"); break; case int n when n % 3 == 0: Console.WriteLine("Buzz"); break; case int n: Console.WriteLine(n); break; } } 

Ini adalah versi aneh dari tugas FizzBuzz yang memproses suatu objek, bukan hanya angka.

Pernyataan switch dapat mencakup beberapa klausa kasus dengan jenis yang sama. Dalam hal ini, kompilator menggabungkan semua pemeriksaan tipe untuk menghindari perhitungan yang tidak perlu:

 public static void FizzBuzz(object o) { // All cases can match only if the value is not null if (o != null) { if (o is string s && (s.Contains("Fizz") || s.Contains("Buzz"))) { Console.WriteLine(s); return; } bool isInt = o is int; int num = isInt ? ((int)o) : 0; if (isInt) { // The type check and unboxing happens only once per group if (num % 5 == 0 && num % 3 == 0) { Console.WriteLine("FizzBuzz"); return; } if (num % 5 == 0) { Console.WriteLine("Fizz"); return; } if (num % 3 == 0) { Console.WriteLine("Buzz"); return; } Console.WriteLine(num); } } } 

Tetapi ada dua hal yang perlu diingat:

1. Kompilator hanya menggabungkan pemeriksaan jenis berurutan, dan jika Anda mencampur klausa kasus dengan jenis yang berbeda, kode berkualitas lebih rendah akan dihasilkan:

 switch (o) { // The generated code is less optimal: // If o is int, then more than one type check and unboxing operation // may happen. case int n when n == 1: return 1; case string s when s == "": return 2; case int n when n == 2: return 3; default: return -1; } 

Kompiler akan mengonversinya sebagai berikut:

jika (o adalah int n && n == 1) mengembalikan 1;
 if (o is string s && s == "") return 2; if (o is int n2 && n2 == 2) return 3; return -1; 

2. Kompiler melakukan segala yang mungkin untuk menghindari masalah urutan yang khas.

 switch (o) { case int n: return 1; // Error: The switch case has already been handled by a previous case. case int n when n == 1: return 2; } 

Namun, kompilator tidak dapat menentukan bahwa satu predikat lebih kuat dari yang lain, dan secara efektif menggantikan klausa kasus berikut:

 switch (o) { case int n when n > 0: return 1; // Will never match, but the compiler won't warn you about it case int n when n > 1: return 2; } 

Brief Pencocokan Pola


  • Pola-pola berikut ini muncul di C # 7: pola konstan, pola tipe, pola variabel, dan pola variabel kosong.
  • Sampel dapat digunakan dalam ekspresi dan blok kasus.
  • Penerapan pola konstan dalam ekspresi adalah untuk tipe nilai jauh dari ideal dalam hal kinerja.
  • Sampel variabel selalu cocok, seseorang harus berhati-hati dengan mereka.
  • Pernyataan switch dapat digunakan untuk mengatur pemeriksaan tipe dengan predikat tambahan pada saat klausa.

Acara persatuan di Moskow - Pertemuan Persatuan Moskow 2018.1


Pada hari Kamis, 11 Oktober, Pertemuan Persatuan Moskow 2018.1 akan diadakan di Sekolah Tinggi Ekonomi. Ini adalah pertemuan pertama pengembang Unity di Moskow musim ini. Tema mitap pertama adalah AR / VR. Anda akan menemukan laporan menarik, komunikasi dengan profesional industri, serta zona demo khusus dari MSI.

Detail

Source: https://habr.com/ru/post/id423229/


All Articles