Praktik terbaik untuk ekspresi reguler di .NET

Mesin ekspresi reguler di .NET adalah alat berfitur lengkap yang kuat yang memproses teks berdasarkan kecocokan pola daripada membandingkan dan mencocokkan teks literal. Dalam kebanyakan kasus, ia melakukan pencocokan pola dengan cepat dan efisien. Namun, dalam beberapa kasus, mesin ekspresi reguler dapat tampak lambat. Dalam kasus ekstrim, bahkan dapat muncul untuk berhenti merespons karena memproses input yang relatif kecil selama berjam-jam atau bahkan berhari-hari.

Artikel ini menguraikan beberapa praktik terbaik yang dapat diadopsi pengembang untuk memastikan bahwa ekspresi reguler mereka mencapai performa optimal.

Peringatan

Saat menggunakan System.Text.RegularExpressions untuk memproses masukan yang tidak tepercaya, berikan batas waktu. Pengguna berbahaya dapat memberikan input ke RegularExpressions, yang menyebabkan serangan Penolakan Layanan. ASP.NET API kerangka kerja Core yang menggunakan RegularExpressions batas waktu.

Pertimbangkan sumber input

Secara umum, ekspresi reguler dapat menerima dua jenis input: dibatasi atau tidak dibatasi. Input yang dibatasi adalah teks yang berasal dari sumber yang diketahui atau dapat diandalkan dan mengikuti format yang telah ditentukan sebelumnya. Input yang tidak dibatasi adalah teks yang berasal dari sumber yang tidak dapat diandalkan, seperti pengguna web, dan mungkin tidak mengikuti format yang telah ditentukan atau diharapkan.

Pola ekspresi reguler sering ditulis agar sesuai dengan input yang valid. Artinya, pengembang memeriksa teks yang ingin mereka cocokkan dan kemudian menulis pola ekspresi reguler yang cocok dengannya. Pengembang kemudian menentukan apakah pola ini memerlukan koreksi atau elaborasi lebih lanjut dengan mengujinya dengan beberapa item input yang valid. Ketika pola cocok dengan semua input yang diduga valid, pola dinyatakan siap produksi, dan dapat disertakan dalam aplikasi yang dirilis. Pendekatan ini membuat pola ekspresi reguler cocok untuk mencocokkan input yang dibatasi. Namun, itu tidak membuatnya cocok untuk mencocokkan input yang tidak dibatasi.

Untuk mencocokkan input yang tidak dibatasi, ekspresi reguler harus menangani tiga jenis teks secara efisien:

  • Teks yang cocok dengan pola ekspresi reguler.
  • Teks yang tidak cocok dengan pola ekspresi reguler.
  • Teks yang hampir cocok dengan pola ekspresi biasa.

Jenis teks terakhir sangat bermasalah untuk ekspresi biasa yang telah ditulis untuk menangani input yang dibatasi. Jika ekspresi reguler itu juga bergantung pada pelacakan balik yang luas, mesin ekspresi reguler dapat menghabiskan jumlah waktu yang tidak biasa (dalam beberapa kasus, berjam-jam atau hari) memproses teks yang tampaknya tidak berbahaya.

Peringatan

Contoh berikut menggunakan ekspresi reguler yang rentan terhadap backtracking yang berlebihan dan kemungkinan akan menolak alamat email yang valid. Anda tidak boleh menggunakannya dalam rutinitas validasi email. Jika Anda ingin ekspresi reguler yang memvalidasi alamat email, lihat Cara: Memverifikasi bahwa String Dalam Format Email yang Valid.

Misalnya, pertimbangkan ekspresi reguler yang umum digunakan tetapi bermasalah untuk memvalidasi alias alamat email. Ekspresi ^[0-9A-Z]([-.\w]*[0-9A-Z])*$ reguler ditulis untuk memproses apa yang dianggap sebagai alamat email yang valid. Alamat email yang valid terdiri dari karakter alfanumerik, diikuti oleh nol atau lebih karakter yang dapat berupa alfanumerik, titik, atau tanda hubung. Ekspresi reguler harus diakhiri dengan karakter alfanumerik. Namun, seperti yang ditunjukkan oleh contoh berikut, meskipun ekspresi reguler ini menangani input yang valid dengan mudah, performanya tidak efisien ketika memproses input yang hampir valid:

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      Stopwatch sw;
      string[] addresses = { "AAAAAAAAAAA@contoso.com",
                             "AAAAAAAAAAaaaaaaaaaa!@contoso.com" };
      // The following regular expression should not actually be used to
      // validate an email address.
      string pattern = @"^[0-9A-Z]([-.\w]*[0-9A-Z])*$";
      string input;

      foreach (var address in addresses) {
         string mailBox = address.Substring(0, address.IndexOf("@"));
         int index = 0;
         for (int ctr = mailBox.Length - 1; ctr >= 0; ctr--) {
            index++;

            input = mailBox.Substring(ctr, index);
            sw = Stopwatch.StartNew();
            Match m = Regex.Match(input, pattern, RegexOptions.IgnoreCase);
            sw.Stop();
            if (m.Success)
               Console.WriteLine("{0,2}. Matched '{1,25}' in {2}",
                                 index, m.Value, sw.Elapsed);
            else
               Console.WriteLine("{0,2}. Failed  '{1,25}' in {2}",
                                 index, input, sw.Elapsed);
         }
         Console.WriteLine();
      }
   }
}

// The example displays output similar to the following:
//     1. Matched '                        A' in 00:00:00.0007122
//     2. Matched '                       AA' in 00:00:00.0000282
//     3. Matched '                      AAA' in 00:00:00.0000042
//     4. Matched '                     AAAA' in 00:00:00.0000038
//     5. Matched '                    AAAAA' in 00:00:00.0000042
//     6. Matched '                   AAAAAA' in 00:00:00.0000042
//     7. Matched '                  AAAAAAA' in 00:00:00.0000042
//     8. Matched '                 AAAAAAAA' in 00:00:00.0000087
//     9. Matched '                AAAAAAAAA' in 00:00:00.0000045
//    10. Matched '               AAAAAAAAAA' in 00:00:00.0000045
//    11. Matched '              AAAAAAAAAAA' in 00:00:00.0000045
//
//     1. Failed  '                        !' in 00:00:00.0000447
//     2. Failed  '                       a!' in 00:00:00.0000071
//     3. Failed  '                      aa!' in 00:00:00.0000071
//     4. Failed  '                     aaa!' in 00:00:00.0000061
//     5. Failed  '                    aaaa!' in 00:00:00.0000081
//     6. Failed  '                   aaaaa!' in 00:00:00.0000126
//     7. Failed  '                  aaaaaa!' in 00:00:00.0000359
//     8. Failed  '                 aaaaaaa!' in 00:00:00.0000414
//     9. Failed  '                aaaaaaaa!' in 00:00:00.0000758
//    10. Failed  '               aaaaaaaaa!' in 00:00:00.0001462
//    11. Failed  '              aaaaaaaaaa!' in 00:00:00.0002885
//    12. Failed  '             Aaaaaaaaaaa!' in 00:00:00.0005780
//    13. Failed  '            AAaaaaaaaaaa!' in 00:00:00.0011628
//    14. Failed  '           AAAaaaaaaaaaa!' in 00:00:00.0022851
//    15. Failed  '          AAAAaaaaaaaaaa!' in 00:00:00.0045864
//    16. Failed  '         AAAAAaaaaaaaaaa!' in 00:00:00.0093168
//    17. Failed  '        AAAAAAaaaaaaaaaa!' in 00:00:00.0185993
//    18. Failed  '       AAAAAAAaaaaaaaaaa!' in 00:00:00.0366723
//    19. Failed  '      AAAAAAAAaaaaaaaaaa!' in 00:00:00.1370108
//    20. Failed  '     AAAAAAAAAaaaaaaaaaa!' in 00:00:00.1553966
//    21. Failed  '    AAAAAAAAAAaaaaaaaaaa!' in 00:00:00.3223372
Imports System.Diagnostics
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim sw As Stopwatch
        Dim addresses() As String = {"AAAAAAAAAAA@contoso.com",
                                   "AAAAAAAAAAaaaaaaaaaa!@contoso.com"}
        ' The following regular expression should not actually be used to 
        ' validate an email address.
        Dim pattern As String = "^[0-9A-Z]([-.\w]*[0-9A-Z])*$"
        Dim input As String

        For Each address In addresses
            Dim mailBox As String = address.Substring(0, address.IndexOf("@"))
            Dim index As Integer = 0
            For ctr As Integer = mailBox.Length - 1 To 0 Step -1
                index += 1
                input = mailBox.Substring(ctr, index)
                sw = Stopwatch.StartNew()
                Dim m As Match = Regex.Match(input, pattern, RegexOptions.IgnoreCase)
                sw.Stop()
                if m.Success Then
                    Console.WriteLine("{0,2}. Matched '{1,25}' in {2}",
                                      index, m.Value, sw.Elapsed)
                Else
                    Console.WriteLine("{0,2}. Failed  '{1,25}' in {2}",
                                      index, input, sw.Elapsed)
                End If
            Next
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays output similar to the following:
'     1. Matched '                        A' in 00:00:00.0007122
'     2. Matched '                       AA' in 00:00:00.0000282
'     3. Matched '                      AAA' in 00:00:00.0000042
'     4. Matched '                     AAAA' in 00:00:00.0000038
'     5. Matched '                    AAAAA' in 00:00:00.0000042
'     6. Matched '                   AAAAAA' in 00:00:00.0000042
'     7. Matched '                  AAAAAAA' in 00:00:00.0000042
'     8. Matched '                 AAAAAAAA' in 00:00:00.0000087
'     9. Matched '                AAAAAAAAA' in 00:00:00.0000045
'    10. Matched '               AAAAAAAAAA' in 00:00:00.0000045
'    11. Matched '              AAAAAAAAAAA' in 00:00:00.0000045
'    
'     1. Failed  '                        !' in 00:00:00.0000447
'     2. Failed  '                       a!' in 00:00:00.0000071
'     3. Failed  '                      aa!' in 00:00:00.0000071
'     4. Failed  '                     aaa!' in 00:00:00.0000061
'     5. Failed  '                    aaaa!' in 00:00:00.0000081
'     6. Failed  '                   aaaaa!' in 00:00:00.0000126
'     7. Failed  '                  aaaaaa!' in 00:00:00.0000359
'     8. Failed  '                 aaaaaaa!' in 00:00:00.0000414
'     9. Failed  '                aaaaaaaa!' in 00:00:00.0000758
'    10. Failed  '               aaaaaaaaa!' in 00:00:00.0001462
'    11. Failed  '              aaaaaaaaaa!' in 00:00:00.0002885
'    12. Failed  '             Aaaaaaaaaaa!' in 00:00:00.0005780
'    13. Failed  '            AAaaaaaaaaaa!' in 00:00:00.0011628
'    14. Failed  '           AAAaaaaaaaaaa!' in 00:00:00.0022851
'    15. Failed  '          AAAAaaaaaaaaaa!' in 00:00:00.0045864
'    16. Failed  '         AAAAAaaaaaaaaaa!' in 00:00:00.0093168
'    17. Failed  '        AAAAAAaaaaaaaaaa!' in 00:00:00.0185993
'    18. Failed  '       AAAAAAAaaaaaaaaaa!' in 00:00:00.0366723
'    19. Failed  '      AAAAAAAAaaaaaaaaaa!' in 00:00:00.1370108
'    20. Failed  '     AAAAAAAAAaaaaaaaaaa!' in 00:00:00.1553966
'    21. Failed  '    AAAAAAAAAAaaaaaaaaaa!' in 00:00:00.3223372

Seperti yang ditunjukkan oleh output dari contoh sebelumnya, mesin ekspresi reguler memproses alias email yang valid dalam interval waktu yang hampir sama terlepas dari panjangnya. Di sisi lain, ketika alamat email yang hampir valid memiliki lebih dari lima karakter, waktu pemrosesan sekitar dua kali lipat untuk setiap karakter tambahan dalam string. Oleh karena itu, string 28 karakter yang hampir valid akan memakan waktu lebih dari satu jam untuk diproses, dan string 33 karakter yang hampir valid akan memakan waktu hampir satu hari untuk diproses.

Karena ekspresi reguler ini dikembangkan hanya dengan mempertimbangkan format input yang akan dicocokkan, ekspresi reguler ini gagal memperhitungkan input yang tidak cocok dengan pola. Pengawasan ini, pada gilirannya, dapat memungkinkan input yang tidak dibatasi yang hampir cocok dengan pola ekspresi reguler untuk menurunkan performa secara signifikan.

Untuk mengatasi masalah ini, lakukan hal berikut:

  • Saat mengembangkan pola, Anda harus mempertimbangkan bagaimana pelacakan balik dapat memengaruhi kinerja mesin ekspresi biasa, terutama jika ekspresi reguler Anda dirancang untuk memproses input yang tidak dibatasi. Untuk informasi selengkapnya, lihat bagian Ambil alih bagian Pelacakan Balik.

  • Uji ekspresi reguler Anda secara menyeluruh menggunakan input yang tidak valid, hampir valid, dan valid. Anda dapat menggunakan Rex untuk menghasilkan input secara acak untuk ekspresi reguler tertentu. Rex adalah alat eksplorasi ekspresi reguler dari Microsoft Research.

Menangani instansiasi objek dengan tepat

Di jantung . Model objek ekspresi reguler NET, adalah System.Text.RegularExpressions.Regex kelas , yang mewakili mesin ekspresi reguler. Seringkali, satu faktor terbesar yang memengaruhi performa ekspresi reguler adalah cara Regex mesin digunakan. Menetapkan ekspresi biasa melibatkan kopling erat mesin ekspresi biasa dengan pola ekspresi biasa. Proses konektor itu, apakah melibatkan pembuatan instans Regex objek dengan meneruskan konstruktornya pola ekspresi reguler atau memanggil metode statis dengan meneruskannya pola ekspresi reguler dan string yang akan dianalisis, adalah dengan kebutuhan yang mahal.

Catatan

Untuk diskusi terperinci tentang implikasi performa penggunaan ekspresi reguler yang ditafsirkan dan dikompilasi, lihat Mengoptimalkan Performa Ekspresi Reguler, Bagian II: Mengambil Muatan Backtracking di blog Tim BCL.

Anda dapat menggabungkan mesin ekspresi reguler dengan pola ekspresi reguler tertentu lalu menggunakan mesin untuk mencocokkan teks dengan beberapa cara:

  • Anda dapat memanggil metode pencocokan pola statis, seperti Regex.Match(String, String). Metode ini tidak memerlukan instans objek ekspresi reguler.

  • Anda dapat membuat instans Regex objek dan memanggil metode pencocokan pola instans dari ekspresi reguler yang ditafsirkan, yang merupakan metode default untuk mengikat mesin ekspresi reguler ke pola ekspresi reguler. Ini menghasilkan ketika Regex objek dibuat tanpa options argumen yang menyertakan Compiled bendera.

  • Anda dapat membuat instans Regex objek dan memanggil metode pencocokan pola instans dari ekspresi reguler yang ditafsirkan. Objek ekspresi reguler mewakili pola yang dikompilasi saat Regex objek dibuat dengan options argumen yang menyertakan Compiled bendera.

  • Anda dapat membuat objek tujuan Regex khusus yang digabungkan erat dengan pola ekspresi reguler tertentu, mengkompilasinya, dan menyimpannya ke rakitan mandiri. Anda dapat memanggil Regex.CompileToAssembly metode untuk mengkompilasi dan menyimpannya.

Cara khusus di mana Anda memanggil metode pencocokan ekspresi reguler dapat memengaruhi performa aplikasi Anda. Bagian berikut membahas kapan harus menggunakan panggilan metode statis, menafsirkan ekspresi reguler, dan menyusun ekspresi reguler untuk meningkatkan kinerja aplikasi Anda.

Penting

Bentuk panggilan metode (statis, ditafsirkan, dikompilasi) mempengaruhi kinerja jika ekspresi reguler yang sama digunakan berulang kali dalam panggilan metode, atau jika aplikasi membuat ekstensif menggunakan objek ekspresi biasa.

Ekspresi reguler statis

Metode ekspresi reguler statis direkomendasikan sebagai alternatif untuk berulang kali instantiating objek ekspresi biasa dengan ekspresi reguler yang sama. Tidak seperti pola ekspresi reguler yang digunakan oleh objek ekspresi reguler, kode operasi atau bahasa perantara umum (CIL) yang dikompilasi dari pola yang digunakan dalam panggilan metode statis di-cache secara internal oleh mesin ekspresi reguler.

Misalnya, penanganan aktivitas sering memanggil metode lain untuk memvalidasi input pengguna. Contoh ini tercermin dalam kode berikut, di mana Button peristiwa kontrol Click digunakan untuk memanggil metode bernama IsValidCurrency, yang memeriksa apakah pengguna telah memasukkan simbol mata uang diikuti oleh setidaknya satu digit desimal.

public void OKButton_Click(object sender, EventArgs e)
{
   if (! String.IsNullOrEmpty(sourceCurrency.Text))
      if (RegexLib.IsValidCurrency(sourceCurrency.Text))
         PerformConversion();
      else
         status.Text = "The source currency value is invalid.";
}
Public Sub OKButton_Click(sender As Object, e As EventArgs) _
           Handles OKButton.Click

    If Not String.IsNullOrEmpty(sourceCurrency.Text) Then
        If RegexLib.IsValidCurrency(sourceCurrency.Text) Then
            PerformConversion()
        Else
            status.Text = "The source currency value is invalid."
        End If
    End If
End Sub

Implementasi metode yang IsValidCurrency tidak efisien ditunjukkan dalam contoh berikut:

Catatan

Setiap panggilan metode menginstansiasi Regex ulang objek dengan pola yang sama. Ini, pada gilirannya, berarti bahwa pola ekspresi reguler harus dikompilasi ulang setiap kali metode dipanggil.

using System;
using System.Text.RegularExpressions;

public class RegexLib
{
   public static bool IsValidCurrency(string currencyValue)
   {
      string pattern = @"\p{Sc}+\s*\d+";
      Regex currencyRegex = new Regex(pattern);
      return currencyRegex.IsMatch(currencyValue);
   }
}
Imports System.Text.RegularExpressions

Public Module RegexLib
    Public Function IsValidCurrency(currencyValue As String) As Boolean
        Dim pattern As String = "\p{Sc}+\s*\d+"
        Dim currencyRegex As New Regex(pattern)
        Return currencyRegex.IsMatch(currencyValue)
    End Function
End Module

Anda harus mengganti kode tidak efisien sebelumnya dengan panggilan ke metode statis Regex.IsMatch(String, String) . Pendekatan ini menghilangkan kebutuhan untuk membuat instans Regex objek setiap kali Anda ingin memanggil metode pencocokan pola, dan memungkinkan mesin ekspresi reguler untuk mengambil versi ekspresi reguler yang dikompilasi dari cache-nya.

using System;
using System.Text.RegularExpressions;

public class RegexLib
{
   public static bool IsValidCurrency(string currencyValue)
   {
      string pattern = @"\p{Sc}+\s*\d+";
      return Regex.IsMatch(currencyValue, pattern);
   }
}
Imports System.Text.RegularExpressions

Public Module RegexLib
    Public Function IsValidCurrency(currencyValue As String) As Boolean
        Dim pattern As String = "\p{Sc}+\s*\d+"
        Return Regex.IsMatch(currencyValue, pattern)
    End Function
End Module

Secara default, 15 pola ekspresi reguler statis terakhir yang paling baru digunakan di-cache. Untuk aplikasi yang memerlukan lebih banyak ekspresi reguler statis yang di-cache, ukuran cache dapat disesuaikan dengan mengatur Regex.CacheSize properti.

Ekspresi \p{Sc}+\s*\d+ reguler yang digunakan dalam contoh ini memverifikasi bahwa string input memiliki simbol mata uang dan setidaknya satu digit desimal. Pola didefinisikan seperti yang ditunjukkan dalam tabel berikut:

Pola Deskripsi
\p{Sc}+ Cocok dengan satu atau beberapa karakter dalam kategori Simbol Unicode, Mata Uang.
\s* Cocok dengan nol atau lebih karakter spasi kosong.
\d+ Cocok dengan satu atau beberapa digit desimal.

Ekspresi reguler yang ditafsirkan vs. dikompilasi

Pola ekspresi reguler yang tidak terikat ke mesin ekspresi reguler melalui spesifikasi opsi ditafsirkan Compiled . Ketika objek ekspresi biasa yang dinstansiasi, mesin ekspresi reguler mengubah ekspresi reguler menjadi satu set kode operasi. Ketika metode instans dipanggil, kode operasi dikonversi ke CIL dan dijalankan oleh pengkompilasi JIT. Demikian pula, ketika metode ekspresi reguler statis dipanggil dan ekspresi reguler tidak dapat ditemukan di cache, mesin ekspresi reguler mengonversi ekspresi reguler ke sekumpulan kode operasi dan menyimpannya di cache. Kemudian mengonversi kode operasi ini ke CIL sehingga pengkompilasi JIT dapat mengeksekusinya. Ekspresi reguler yang ditafsirkan mengurangi waktu startup dengan mengorbankan waktu eksekusi yang lebih lambat. Karena proses ini, mereka paling baik digunakan ketika ekspresi reguler digunakan dalam sejumlah kecil panggilan metode, atau jika jumlah panggilan yang tepat ke metode ekspresi reguler tidak diketahui tetapi diperkirakan kecil. Ketika jumlah panggilan metode meningkat, keuntungan kinerja dari waktu startup yang berkurang melampaui kecepatan eksekusi yang lebih lambat.

Pola ekspresi reguler yang tidak terikat ke mesin ekspresi reguler melalui spesifikasi Compiled opsi ditafsirkan. Oleh karena itu, ketika objek ekspresi reguler dibuat, atau ketika metode ekspresi reguler statis dipanggil dan ekspresi reguler tidak dapat ditemukan di cache, mesin ekspresi reguler mengonversi ekspresi reguler ke serangkaian kode operasi perantara. Kode-kode ini kemudian dikonversi ke CIL. Ketika metode dipanggil, pengkompilasi JIT menjalankan CIL. Berbeda dengan ekspresi reguler yang ditafsirkan, ekspresi reguler yang dikompilasi meningkatkan waktu startup tetapi menjalankan metode pencocokan pola individu lebih cepat. Akibatnya, manfaat kinerja yang dihasilkan dari kompilasi ekspresi reguler meningkat sebanding dengan jumlah metode ekspresi reguler yang disebut.

Untuk meringkas, kami sarankan Anda menggunakan ekspresi reguler yang ditafsirkan ketika Anda memanggil metode ekspresi reguler dengan ekspresi reguler tertentu yang relatif jarang. Anda harus menggunakan ekspresi reguler yang dikompilasi ketika Anda memanggil metode ekspresi reguler dengan ekspresi reguler tertentu yang relatif sering. Sulit untuk menentukan ambang batas yang tepat di mana kecepatan eksekusi yang lebih lambat dari ekspresi reguler yang ditafsirkan melebihi keuntungan dari pengurangan waktu startup mereka, atau ambang batas di mana waktu startup yang lebih lambat dari ekspresi reguler yang dikompilasi melebihi keuntungan dari kecepatan eksekusi mereka yang lebih cepat. Ini tergantung pada berbagai faktor, termasuk kompleksitas ekspresi reguler dan data spesifik yang diprosesnya. Untuk menentukan apakah ekspresi reguler yang ditafsirkan atau dikompilasi menawarkan performa terbaik untuk skenario aplikasi tertentu, Anda dapat menggunakan Stopwatch kelas untuk membandingkan waktu eksekusinya.

Contoh berikut membandingkan performa ekspresi reguler yang dikompilasi dan ditafsirkan saat membaca 10 kalimat pertama dan ketika membaca semua kalimat dalam teks The Financier theodore Dreiser. Seperti yang ditunjukkan oleh output dari contoh, ketika hanya 10 panggilan yang dilakukan untuk metode pencocokan ekspresi reguler, ekspresi reguler yang ditafsirkan menawarkan performa yang lebih baik daripada ekspresi reguler yang dikompilasi. Namun, ekspresi reguler yang dikompilasi menawarkan kinerja yang lebih baik ketika sejumlah besar panggilan (dalam hal ini, lebih dari 13.000) dibuat.

using System;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      string pattern = @"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]";
      Stopwatch sw;
      Match match;
      int ctr;

      StreamReader inFile = new StreamReader(@".\Dreiser_TheFinancier.txt");
      string input = inFile.ReadToEnd();
      inFile.Close();

      // Read first ten sentences with interpreted regex.
      Console.WriteLine("10 Sentences with Interpreted Regex:");
      sw = Stopwatch.StartNew();
      Regex int10 = new Regex(pattern, RegexOptions.Singleline);
      match = int10.Match(input);
      for (ctr = 0; ctr <= 9; ctr++) {
         if (match.Success)
            // Do nothing with the match except get the next match.
            match = match.NextMatch();
         else
            break;
      }
      sw.Stop();
      Console.WriteLine("   {0} matches in {1}", ctr, sw.Elapsed);

      // Read first ten sentences with compiled regex.
      Console.WriteLine("10 Sentences with Compiled Regex:");
      sw = Stopwatch.StartNew();
      Regex comp10 = new Regex(pattern,
                   RegexOptions.Singleline | RegexOptions.Compiled);
      match = comp10.Match(input);
      for (ctr = 0; ctr <= 9; ctr++) {
         if (match.Success)
            // Do nothing with the match except get the next match.
            match = match.NextMatch();
         else
            break;
      }
      sw.Stop();
      Console.WriteLine("   {0} matches in {1}", ctr, sw.Elapsed);

      // Read all sentences with interpreted regex.
      Console.WriteLine("All Sentences with Interpreted Regex:");
      sw = Stopwatch.StartNew();
      Regex intAll = new Regex(pattern, RegexOptions.Singleline);
      match = intAll.Match(input);
      int matches = 0;
      while (match.Success) {
         matches++;
         // Do nothing with the match except get the next match.
         match = match.NextMatch();
      }
      sw.Stop();
      Console.WriteLine("   {0:N0} matches in {1}", matches, sw.Elapsed);

      // Read all sentences with compiled regex.
      Console.WriteLine("All Sentences with Compiled Regex:");
      sw = Stopwatch.StartNew();
      Regex compAll = new Regex(pattern,
                      RegexOptions.Singleline | RegexOptions.Compiled);
      match = compAll.Match(input);
      matches = 0;
      while (match.Success) {
         matches++;
         // Do nothing with the match except get the next match.
         match = match.NextMatch();
      }
      sw.Stop();
      Console.WriteLine("   {0:N0} matches in {1}", matches, sw.Elapsed);
   }
}
// The example displays the following output:
//       10 Sentences with Interpreted Regex:
//          10 matches in 00:00:00.0047491
//       10 Sentences with Compiled Regex:
//          10 matches in 00:00:00.0141872
//       All Sentences with Interpreted Regex:
//          13,443 matches in 00:00:01.1929928
//       All Sentences with Compiled Regex:
//          13,443 matches in 00:00:00.7635869
//
//       >compare1
//       10 Sentences with Interpreted Regex:
//          10 matches in 00:00:00.0046914
//       10 Sentences with Compiled Regex:
//          10 matches in 00:00:00.0143727
//       All Sentences with Interpreted Regex:
//          13,443 matches in 00:00:01.1514100
//       All Sentences with Compiled Regex:
//          13,443 matches in 00:00:00.7432921
Imports System.Diagnostics
Imports System.IO
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim pattern As String = "\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]"
        Dim sw As Stopwatch
        Dim match As Match
        Dim ctr As Integer

        Dim inFile As New StreamReader(".\Dreiser_TheFinancier.txt")
        Dim input As String = inFile.ReadToEnd()
        inFile.Close()

        ' Read first ten sentences with interpreted regex.
        Console.WriteLine("10 Sentences with Interpreted Regex:")
        sw = Stopwatch.StartNew()
        Dim int10 As New Regex(pattern, RegexOptions.SingleLine)
        match = int10.Match(input)
        For ctr = 0 To 9
            If match.Success Then
                ' Do nothing with the match except get the next match.
                match = match.NextMatch()
            Else
                Exit For
            End If
        Next
        sw.Stop()
        Console.WriteLine("   {0} matches in {1}", ctr, sw.Elapsed)

        ' Read first ten sentences with compiled regex.
        Console.WriteLine("10 Sentences with Compiled Regex:")
        sw = Stopwatch.StartNew()
        Dim comp10 As New Regex(pattern,
                     RegexOptions.SingleLine Or RegexOptions.Compiled)
        match = comp10.Match(input)
        For ctr = 0 To 9
            If match.Success Then
                ' Do nothing with the match except get the next match.
                match = match.NextMatch()
            Else
                Exit For
            End If
        Next
        sw.Stop()
        Console.WriteLine("   {0} matches in {1}", ctr, sw.Elapsed)

        ' Read all sentences with interpreted regex.
        Console.WriteLine("All Sentences with Interpreted Regex:")
        sw = Stopwatch.StartNew()
        Dim intAll As New Regex(pattern, RegexOptions.SingleLine)
        match = intAll.Match(input)
        Dim matches As Integer = 0
        Do While match.Success
            matches += 1
            ' Do nothing with the match except get the next match.
            match = match.NextMatch()
        Loop
        sw.Stop()
        Console.WriteLine("   {0:N0} matches in {1}", matches, sw.Elapsed)

        ' Read all sentences with compiled regex.
        Console.WriteLine("All Sentences with Compiled Regex:")
        sw = Stopwatch.StartNew()
        Dim compAll As New Regex(pattern,
                       RegexOptions.SingleLine Or RegexOptions.Compiled)
        match = compAll.Match(input)
        matches = 0
        Do While match.Success
            matches += 1
            ' Do nothing with the match except get the next match.
            match = match.NextMatch()
        Loop
        sw.Stop()
        Console.WriteLine("   {0:N0} matches in {1}", matches, sw.Elapsed)
    End Sub
End Module
' The example displays output like the following:
'       10 Sentences with Interpreted Regex:
'          10 matches in 00:00:00.0047491
'       10 Sentences with Compiled Regex:
'          10 matches in 00:00:00.0141872
'       All Sentences with Interpreted Regex:
'          13,443 matches in 00:00:01.1929928
'       All Sentences with Compiled Regex:
'          13,443 matches in 00:00:00.7635869
'       
'       >compare1
'       10 Sentences with Interpreted Regex:
'          10 matches in 00:00:00.0046914
'       10 Sentences with Compiled Regex:
'          10 matches in 00:00:00.0143727
'       All Sentences with Interpreted Regex:
'          13,443 matches in 00:00:01.1514100
'       All Sentences with Compiled Regex:
'          13,443 matches in 00:00:00.7432921

Pola ekspresi reguler yang digunakan dalam contoh, \b(\w+((\r?\n)|,?\s))*\w+[.?:;!], didefinisikan seperti yang ditunjukkan dalam tabel berikut:

Pola Deskripsi
\b Memulai pencocokan dalam batas kata.
\w+ Cocok dengan satu atau beberapa karakter kata.
(\r?\n)|,?\s) Cocok dengan nol atau satu pengembalian pengangkutan diikuti oleh karakter baris baru, atau nol atau satu koma diikuti oleh karakter spasi putih.
(\w+((\r?\n)|,?\s))* Cocok dengan nol atau lebih kemunculan satu atau beberapa karakter kata yang diikuti dengan nol atau satu pengembalian pengangkutan dan karakter garis baru, atau dengan nol atau satu koma diikuti oleh karakter spasi putih.
\w+ Cocok dengan satu atau beberapa karakter kata.
[.?:;!] Cocok dengan titik, tanda tanya, titik dua, titik koma, atau tanda seru.

Ekspresi reguler: Dikompilasi ke assembly

.NET juga memungkinkan Anda membuat assembly yang berisi ekspresi reguler yang dikompilasi. Kemampuan ini memindahkan hit performa kompilasi ekspresi reguler dari waktu proses ke waktu desain. Namun, ini juga melibatkan beberapa pekerjaan tambahan. Anda harus menentukan ekspresi reguler terlebih dahulu dan mengkompilasinya ke rakitan. Kompiler kemudian dapat mereferensikan assembly ini saat menyusun kode sumber yang menggunakan ekspresi reguler assembly. Setiap ekspresi reguler yang dikompilasi dalam assembly diwakili oleh kelas yang berasal dari Regex.

Untuk mengkompilasi ekspresi reguler ke assembly, Anda memanggil Regex.CompileToAssembly(RegexCompilationInfo[], AssemblyName) metode dan meneruskannya array RegexCompilationInfo objek dan AssemblyName objek. Objek RegexCompilationInfo mewakili ekspresi reguler yang akan dikompilasi, dan AssemblyName objek yang berisi informasi tentang rakitan yang akan dibuat.

Kami menyarankan Anda menyusun ekspresi reguler ke assembly dalam situasi berikut:

  • Jika Anda adalah pengembang komponen yang ingin membuat pustaka ekspresi reguler yang dapat digunakan kembali.
  • Jika Anda mengharapkan metode pencocokan pola ekspresi reguler Anda disebut jumlah yang tidak ditentukan—di mana saja dari sekali atau dua kali hingga ribuan atau puluhan ribu kali. Tidak seperti ekspresi reguler yang dikompilasi atau ditafsirkan, ekspresi reguler yang dikompilasi ke rakitan terpisah menawarkan performa yang konsisten terlepas dari jumlah panggilan metode.

Jika Anda menggunakan ekspresi reguler yang dikompilasi untuk mengoptimalkan performa, Anda tidak boleh menggunakan pantulan untuk membuat rakitan, memuat mesin ekspresi reguler, dan menjalankan metode pencocokan polanya. Menghindari refleksi mengharuskan Anda tidak membangun pola ekspresi reguler secara dinamis, dan Anda menentukan opsi pencocokan pola apa pun, seperti pencocokan pola yang tidak peka huruf besar/kecil, pada saat perakitan dibuat. Ini juga mengharuskan Anda memisahkan kode yang membuat assembly dari kode yang menggunakan ekspresi reguler.

Contoh berikut menunjukkan cara membuat assembly yang berisi ekspresi reguler yang dikompilasi. Ini membuat rakitan bernama RegexLib.dll dengan satu kelas ekspresi reguler, SentencePattern. Kelas ini berisi pola ekspresi reguler pencocokan kalimat yang digunakan di bagian Ekspresi Reguler yang Ditafsirkan vs. Dikompilasi .

using System;
using System.Reflection;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      RegexCompilationInfo SentencePattern =
                           new RegexCompilationInfo(@"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]",
                                                    RegexOptions.Multiline,
                                                    "SentencePattern",
                                                    "Utilities.RegularExpressions",
                                                    true);
      RegexCompilationInfo[] regexes = { SentencePattern };
      AssemblyName assemName = new AssemblyName("RegexLib, Version=1.0.0.1001, Culture=neutral, PublicKeyToken=null");
      Regex.CompileToAssembly(regexes, assemName);
   }
}
Imports System.Reflection
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim SentencePattern As New RegexCompilationInfo("\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]",
                                                        RegexOptions.Multiline,
                                                        "SentencePattern",
                                                        "Utilities.RegularExpressions",
                                                        True)
        Dim regexes() As RegexCompilationInfo = {SentencePattern}
        Dim assemName As New AssemblyName("RegexLib, Version=1.0.0.1001, Culture=neutral, PublicKeyToken=null")
        Regex.CompileToAssembly(regexes, assemName)
    End Sub
End Module

Ketika contoh dikompilasi menjadi dapat dieksekusi dan dijalankan, contoh tersebut akan membuat assembly bernama RegexLib.dll. Kelas Utilities.RegularExpressions.SentencePattern yang berasal dari Regex mewakili ekspresi reguler. Contoh berikut kemudian menggunakan ekspresi reguler yang dikompilasi untuk mengekstrak kalimat dari teks The Financier theodore Dreiser:

using System;
using System.IO;
using System.Text.RegularExpressions;
using Utilities.RegularExpressions;

public class Example
{
   public static void Main()
   {
      SentencePattern pattern = new SentencePattern();
      StreamReader inFile = new StreamReader(@".\Dreiser_TheFinancier.txt");
      string input = inFile.ReadToEnd();
      inFile.Close();

      MatchCollection matches = pattern.Matches(input);
      Console.WriteLine("Found {0:N0} sentences.", matches.Count);
   }
}
// The example displays the following output:
//      Found 13,443 sentences.
Imports System.IO
Imports System.Text.RegularExpressions
Imports Utilities.RegularExpressions

Module Example
    Public Sub Main()
        Dim pattern As New SentencePattern()
        Dim inFile As New StreamReader(".\Dreiser_TheFinancier.txt")
        Dim input As String = inFile.ReadToEnd()
        inFile.Close()

        Dim matches As MatchCollection = pattern.Matches(input)
        Console.WriteLine("Found {0:N0} sentences.", matches.Count)
    End Sub
End Module
' The example displays the following output:
'      Found 13,443 sentences.

Bertanggung jawab atas pelacakan balik

Biasanya, mesin ekspresi biasa menggunakan perkembangan linier untuk bergerak melalui string input dan membandingkannya dengan pola ekspresi biasa. Namun, ketika kuantifier yang tidak ditentukan seperti *, , +dan ? digunakan dalam pola ekspresi reguler, mesin ekspresi reguler mungkin menyerahkan sebagian kecocokan parsial yang berhasil dan kembali ke status yang disimpan sebelumnya untuk mencari kecocokan yang berhasil untuk seluruh pola. Proses ini dikenal sebagai pengelogan.

Tip

Untuk informasi selengkapnya tentang backtracking, lihat Detail perilaku ekspresi reguler dan Backtracking. Untuk diskusi terperinci tentang backtracking, lihat posting blog Peningkatan Ekspresi Reguler di .NET 7 dan Mengoptimalkan Performa Ekspresi Reguler.

Dukungan untuk pelacakan balik memberikan kekuatan ekspresi reguler dan fleksibilitas. Ini juga menempatkan tanggung jawab untuk mengendalikan pengoperasian mesin ekspresi reguler di tangan pengembang ekspresi reguler. Karena pengembang sering tidak menyadari tanggung jawab ini, penyalahgunaan mereka mundur atau ketergantungan pada pelacakan balik yang berlebihan sering memainkan peran paling signifikan dalam menurunkan kinerja ekspresi reguler. Dalam skenario terburuk, waktu eksekusi dapat berlipat ganda untuk setiap karakter tambahan dalam string input. Bahkan, dengan menggunakan backtracking secara berlebihan, mudah untuk membuat perulangan terprogram yang setara dengan perulangan tanpa akhir jika input hampir cocok dengan pola ekspresi reguler. Mesin ekspresi reguler mungkin membutuhkan waktu berjam-jam atau bahkan berjam-hari untuk memproses string input yang relatif singkat.

Seringkali, aplikasi membayar penalti performa untuk menggunakan backtracking meskipun backtracking tidak penting untuk pertandingan. Misalnya, ekspresi \b\p{Lu}\w*\b reguler cocok dengan semua kata yang dimulai dengan karakter huruf besar, seperti yang ditunjukkan oleh tabel berikut:

Pola Deskripsi
\b Memulai pencocokan dalam batas kata.
\p{Lu} Cocok dengan karakter huruf besar.
\w* Cocok dengan nol atau lebih karakter kata.
\b Mengakhiri pencocokan dalam batas kata.

Karena batas kata tidak sama dengan, atau subset, karakter kata, tidak ada kemungkinan bahwa mesin ekspresi reguler akan melewati batas kata saat mencocokkan karakter kata. Oleh karena itu untuk ekspresi reguler ini, backtracking tidak pernah dapat berkontribusi pada keberhasilan keseluruhan dari kecocokan apa pun. Ini hanya dapat menurunkan performa karena mesin ekspresi reguler dipaksa untuk menyimpan statusnya untuk setiap kecocokan awal yang berhasil dari karakter kata.

Jika Anda menentukan bahwa backtracking tidak diperlukan, Anda dapat menonaktifkannya dengan beberapa cara:

  • Dengan mengatur RegexOptions.NonBacktracking opsi (diperkenalkan di .NET 7). Untuk informasi selengkapnya, lihat Mode nonbacktracking.

  • Dengan menggunakan (?>subexpression) elemen bahasa, yang dikenal sebagai grup atomik. Contoh berikut mengurai string input dengan menggunakan dua ekspresi reguler. Yang pertama, \b\p{Lu}\w*\b, mengandalkan pelacakan balik. Yang kedua, \b\p{Lu}(?>\w*)\b, menonaktifkan pelacakan balik. Seperti yang ditunjukkan oleh output dari contoh, keduanya menghasilkan hasil yang sama:

    using System;
    using System.Text.RegularExpressions;
    
    public class Example
    {
       public static void Main()
       {
          string input = "This this word Sentence name Capital";
          string pattern = @"\b\p{Lu}\w*\b";
          foreach (Match match in Regex.Matches(input, pattern))
             Console.WriteLine(match.Value);
    
          Console.WriteLine();
    
          pattern = @"\b\p{Lu}(?>\w*)\b";
          foreach (Match match in Regex.Matches(input, pattern))
             Console.WriteLine(match.Value);
       }
    }
    // The example displays the following output:
    //       This
    //       Sentence
    //       Capital
    //
    //       This
    //       Sentence
    //       Capital
    
    Imports System.Text.RegularExpressions
    
    Module Example
        Public Sub Main()
            Dim input As String = "This this word Sentence name Capital"
            Dim pattern As String = "\b\p{Lu}\w*\b"
            For Each match As Match In Regex.Matches(input, pattern)
                Console.WriteLine(match.Value)
            Next
            Console.WriteLine()
    
            pattern = "\b\p{Lu}(?>\w*)\b"
            For Each match As Match In Regex.Matches(input, pattern)
                Console.WriteLine(match.Value)
            Next
        End Sub
    End Module
    ' The example displays the following output:
    '       This
    '       Sentence
    '       Capital
    '       
    '       This
    '       Sentence
    '       Capital
    

Dalam banyak kasus, pelacakan balik sangat penting untuk mencocokkan pola ekspresi biasa dengan teks input. Namun, pelacakan balik yang berlebihan dapat sangat menurunkan kinerja dan menciptakan kesan bahwa aplikasi telah berhenti merespons. Secara khusus, masalah ini muncul ketika kuantifier disarangkan dan teks yang cocok dengan subekspresi luar adalah subset teks yang cocok dengan subekspresi dalam.

Peringatan

Selain menghindari backtracking yang berlebihan, Anda harus menggunakan fitur batas waktu untuk memastikan bahwa backtracking yang berlebihan tidak sangat menurunkan performa ekspresi reguler. Untuk informasi selengkapnya, lihat bagian Menggunakan nilai batas waktu.

Misalnya, pola ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$ ekspresi reguler dimaksudkan untuk mencocokkan nomor bagian yang terdiri dari sedikitnya satu karakter alfanumerik. Setiap karakter tambahan dapat terdiri dari karakter alfanumerik, tanda hubung, garis bawah, atau periode, meskipun karakter terakhir harus alfanumerik. Tanda dolar mengakhiri nomor bagian. Dalam beberapa kasus, pola ekspresi reguler ini dapat menunjukkan performa yang buruk karena kuantifier bersarang, dan karena subekspresi [0-9A-Z] adalah subset dari subekspresi [-.\w]*.

Dalam kasus ini, Anda dapat mengoptimalkan kinerja ekspresi reguler dengan menghapus pembilang bertumpuk dan mengganti subekspresi luar dengan lookahead lebar nol atau pernyataan lookbehind. Lookahead dan lookbehind pernyataan adalah jangkar. Mereka tidak memindahkan penunjuk dalam string input tetapi sebaliknya melihat ke depan atau di belakang untuk memeriksa apakah kondisi tertentu terpenuhi. Misalnya, ekspresi reguler nomor bagian dapat ditulis ulang sebagai ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$. Pola ekspresi reguler ini didefinisikan seperti yang ditunjukkan dalam tabel berikut:

Pola Deskripsi
^ Mulai pencocokan di awal string input.
[0-9A-Z] Cocokkan karakter alfanumerik. Nomor bagian harus terdiri dari sedikitnya karakter ini.
[-.\w]* Cocokkan nol atau lebih kejadian dari karakter kata, tanda hubung, atau titik apa pun.
\$ Cocok dengan tanda dolar.
(?<=[0-9A-Z]) Lihat di balik tanda dolar akhir untuk memastikan bahwa karakter sebelumnya adalah alfanumerik.
$ Akhiri kecocokan di akhir string input.

Contoh berikut mengilustrasikan penggunaan ekspresi reguler ini untuk mencocokkan array yang berisi kemungkinan angka bagian:

using System;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      string pattern = @"^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$";
      string[] partNos = { "A1C$", "A4", "A4$", "A1603D$", "A1603D#" };

      foreach (var input in partNos) {
         Match match = Regex.Match(input, pattern);
         if (match.Success)
            Console.WriteLine(match.Value);
         else
            Console.WriteLine("Match not found.");
      }
   }
}
// The example displays the following output:
//       A1C$
//       Match not found.
//       A4$
//       A1603D$
//       Match not found.
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim pattern As String = "^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$"
        Dim partNos() As String = {"A1C$", "A4", "A4$", "A1603D$",
                                    "A1603D#"}

        For Each input As String In partNos
            Dim match As Match = Regex.Match(input, pattern)
            If match.Success Then
                Console.WriteLine(match.Value)
            Else
                Console.WriteLine("Match not found.")
            End If
        Next
    End Sub
End Module
' The example displays the following output:
'       A1C$
'       Match not found.
'       A4$
'       A1603D$
'       Match not found.

Bahasa ekspresi reguler di .NET mencakup elemen bahasa berikut yang dapat Anda gunakan untuk menghilangkan pembilang bertumpuk. Untuk informasi selengkapnya, lihat Konstruksi pengelompokan.

Elemen bahasa Deskripsi
(?= subexpression ) Lookahead positif lebar nol. Melihat ke depan posisi saat ini untuk menentukan apakah subexpression cocok dengan string input.
(?! subexpression ) Kepala tampilan negatif lebar nol. Lihat ke depan posisi saat ini untuk menentukan apakah subexpression tidak cocok dengan string input.
(?<= subexpression ) Tampilan positif lebar nolbehind. Melihat ke belakang posisi saat ini untuk menentukan apakah subexpression cocok dengan string input.
(?<! subexpression ) Tampilan negatif lebar nolbehind. Melihat ke belakang posisi saat ini untuk menentukan apakah subexpression tidak cocok dengan string input.

Gunakan nilai waktu habis

Jika ekspresi reguler Anda memproses input yang hampir sesuai dengan pola ekspresi biasa, seringkali dapat mengandalkan pelacakan balik yang berlebihan, yang berdampak pada kinerjanya secara signifikan. Selain mempertimbangkan dengan cermat penggunaan backtracking dan pengujian ekspresi reguler terhadap input yang hampir cocok, Anda harus selalu menetapkan nilai waktu habis untuk meminimalkan efek backtracking yang berlebihan, jika itu terjadi.

Interval batas waktu ekspresi reguler menentukan periode waktu yang akan dicari mesin ekspresi reguler untuk satu kecocokan sebelum waktu habis. Bergantung pada pola ekspresi reguler dan teks input, waktu eksekusi mungkin melebihi interval waktu habis yang ditentukan, tetapi tidak akan menghabiskan lebih banyak waktu untuk mundur daripada interval waktu habis yang ditentukan. Interval batas waktu default adalah Regex.InfiniteMatchTimeout, yang berarti bahwa ekspresi reguler tidak akan kehabisan waktu. Anda dapat mengambil alih nilai ini dan menentukan interval waktu habis sebagai berikut:

Jika Anda telah menentukan interval waktu habis dan kecocokan tidak ditemukan di akhir interval tersebut, metode ekspresi reguler akan melemparkan RegexMatchTimeoutException pengecualian. Dalam handler pengecualian, Anda dapat memilih untuk mencoba kembali kecocokan dengan interval waktu habis yang lebih lama, meninggalkan upaya pencocokan dan mengasumsikan bahwa tidak ada kecocokan, atau meninggalkan upaya pencocokan dan mencatat informasi pengecualian untuk analisis di masa mendatang.

Contoh berikut menetapkan GetWordData metode yang membuat instans ekspresi reguler dengan interval waktu habis 350 milidetik untuk menghitung jumlah kata dan jumlah rata-rata karakter dalam kata dalam dokumen teks. Jika waktu operasi pencocokan habis, interval waktu habis ditingkatkan sebesar 350 milidetik dan Regex objek diinstansiasi ulang. Jika interval batas waktu baru melebihi satu detik, metode akan menggagalkan kembali pengecualian untuk pemanggil.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      RegexUtilities util = new RegexUtilities();
      string title = "Doyle - The Hound of the Baskervilles.txt";
      try {
         var info = util.GetWordData(title);
         Console.WriteLine("Words:               {0:N0}", info.Item1);
         Console.WriteLine("Average Word Length: {0:N2} characters", info.Item2);
      }
      catch (IOException e) {
         Console.WriteLine("IOException reading file '{0}'", title);
         Console.WriteLine(e.Message);
      }
      catch (RegexMatchTimeoutException e) {
         Console.WriteLine("The operation timed out after {0:N0} milliseconds",
                           e.MatchTimeout.TotalMilliseconds);
      }
   }
}

public class RegexUtilities
{
   public Tuple<int, double> GetWordData(string filename)
   {
      const int MAX_TIMEOUT = 1000;   // Maximum timeout interval in milliseconds.
      const int INCREMENT = 350;      // Milliseconds increment of timeout.

      List<string> exclusions = new List<string>( new string[] { "a", "an", "the" });
      int[] wordLengths = new int[29];        // Allocate an array of more than ample size.
      string input = null;
      StreamReader sr = null;
      try {
         sr = new StreamReader(filename);
         input = sr.ReadToEnd();
      }
      catch (FileNotFoundException e) {
         string msg = String.Format("Unable to find the file '{0}'", filename);
         throw new IOException(msg, e);
      }
      catch (IOException e) {
         throw new IOException(e.Message, e);
      }
      finally {
         if (sr != null) sr.Close();
      }

      int timeoutInterval = INCREMENT;
      bool init = false;
      Regex rgx = null;
      Match m = null;
      int indexPos = 0;
      do {
         try {
            if (! init) {
               rgx = new Regex(@"\b\w+\b", RegexOptions.None,
                               TimeSpan.FromMilliseconds(timeoutInterval));
               m = rgx.Match(input, indexPos);
               init = true;
            }
            else {
               m = m.NextMatch();
            }
            if (m.Success) {
               if ( !exclusions.Contains(m.Value.ToLower()))
                  wordLengths[m.Value.Length]++;

               indexPos += m.Length + 1;
            }
         }
         catch (RegexMatchTimeoutException e) {
            if (e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT) {
               timeoutInterval += INCREMENT;
               init = false;
            }
            else {
               // Rethrow the exception.
               throw;
            }
         }
      } while (m.Success);

      // If regex completed successfully, calculate number of words and average length.
      int nWords = 0;
      long totalLength = 0;

      for (int ctr = wordLengths.GetLowerBound(0); ctr <= wordLengths.GetUpperBound(0); ctr++) {
         nWords += wordLengths[ctr];
         totalLength += ctr * wordLengths[ctr];
      }
      return new Tuple<int, double>(nWords, totalLength/nWords);
   }
}
Imports System.Collections.Generic
Imports System.IO
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim util As New RegexUtilities()
        Dim title As String = "Doyle - The Hound of the Baskervilles.txt"
        Try
            Dim info = util.GetWordData(title)
            Console.WriteLine("Words:               {0:N0}", info.Item1)
            Console.WriteLine("Average Word Length: {0:N2} characters", info.Item2)
        Catch e As IOException
            Console.WriteLine("IOException reading file '{0}'", title)
            Console.WriteLine(e.Message)
        Catch e As RegexMatchTimeoutException
            Console.WriteLine("The operation timed out after {0:N0} milliseconds",
                              e.MatchTimeout.TotalMilliseconds)
        End Try
    End Sub
End Module

Public Class RegexUtilities
    Public Function GetWordData(filename As String) As Tuple(Of Integer, Double)
        Const MAX_TIMEOUT As Integer = 1000  ' Maximum timeout interval in milliseconds.
        Const INCREMENT As Integer = 350     ' Milliseconds increment of timeout.

        Dim exclusions As New List(Of String)({"a", "an", "the"})
        Dim wordLengths(30) As Integer        ' Allocate an array of more than ample size.
        Dim input As String = Nothing
        Dim sr As StreamReader = Nothing
        Try
            sr = New StreamReader(filename)
            input = sr.ReadToEnd()
        Catch e As FileNotFoundException
            Dim msg As String = String.Format("Unable to find the file '{0}'", filename)
            Throw New IOException(msg, e)
        Catch e As IOException
            Throw New IOException(e.Message, e)
        Finally
            If sr IsNot Nothing Then sr.Close()
        End Try

        Dim timeoutInterval As Integer = INCREMENT
        Dim init As Boolean = False
        Dim rgx As Regex = Nothing
        Dim m As Match = Nothing
        Dim indexPos As Integer = 0
        Do
            Try
                If Not init Then
                    rgx = New Regex("\b\w+\b", RegexOptions.None,
                                    TimeSpan.FromMilliseconds(timeoutInterval))
                    m = rgx.Match(input, indexPos)
                    init = True
                Else
                    m = m.NextMatch()
                End If
                If m.Success Then
                    If Not exclusions.Contains(m.Value.ToLower()) Then
                        wordLengths(m.Value.Length) += 1
                    End If
                    indexPos += m.Length + 1
                End If
            Catch e As RegexMatchTimeoutException
                If e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT Then
                    timeoutInterval += INCREMENT
                    init = False
                Else
                    ' Rethrow the exception.
                    Throw
                End If
            End Try
        Loop While m.Success

        ' If regex completed successfully, calculate number of words and average length.
        Dim nWords As Integer
        Dim totalLength As Long

        For ctr As Integer = wordLengths.GetLowerBound(0) To wordLengths.GetUpperBound(0)
            nWords += wordLengths(ctr)
            totalLength += ctr * wordLengths(ctr)
        Next
        Return New Tuple(Of Integer, Double)(nWords, totalLength / nWords)
    End Function
End Class

Tangkap hanya jika diperlukan

Ekspresi reguler dalam konstruksi pengelompokan dukungan .NET, yang memungkinkan Anda mengelompokkan pola ekspresi reguler ke dalam satu atau beberapa subekspresi. Konstruksi pengelompokan yang paling umum digunakan dalam bahasa ekspresi reguler .NET adalah (subekspresi), yang menetapkan grup pengambilan bernomor, dan (?<subekspresi>nama), yang menetapkan grup pengambilan bernama. Pengelompokan konstruksi sangat penting untuk menciptakan backreferences dan untuk menetapkan subekspresi yang pembilangnya diterapkan.

Namun, penggunaan elemen bahasa ini memiliki biaya. Mereka menyebabkan objek yang GroupCollection dikembalikan oleh Match.Groups properti diisi dengan tangkapan yang tidak disebutkan namanya atau bernama terbaru. Jika konstruksi pengelompokan tunggal telah mengambil beberapa substring dalam string input, mereka juga mengisi CaptureCollection objek yang dikembalikan oleh Group.Captures properti grup penangkapan tertentu dengan beberapa Capture objek.

Seringkali, konstruksi pengelompokan hanya digunakan dalam ekspresi reguler sehingga kuantifer dapat diterapkan padanya. Grup yang diambil oleh subekspresi ini tidak digunakan nanti. Misalnya, ekspresi \b(\w+[;,]?\s?)+[.?!] reguler dirancang untuk menangkap seluruh kalimat. Tabel berikut ini menjelaskan elemen bahasa dalam pola ekspresi reguler ini dan efeknya pada Match objek Match.Groups dan Group.Captures koleksi:

Pola Deskripsi
\b Memulai pencocokan dalam batas kata.
\w+ Cocok dengan satu atau beberapa karakter kata.
[;,]? Cocok dengan nol atau satu koma atau titik koma.
\s? Cocok dengan nol atau satu karakter spasi putih.
(\w+[;,]?\s?)+ Cocok dengan satu atau beberapa kemunculan satu atau beberapa karakter kata diikuti dengan koma opsional atau titik koma diikuti oleh karakter spasi putih opsional. Pola ini mendefinisikan grup penangkapan pertama, yang diperlukan sehingga kombinasi beberapa karakter kata (yaitu, kata) diikuti dengan simbol tanda baca opsional akan diulang hingga mesin ekspresi reguler mencapai akhir kalimat.
[.?!] Cocok dengan titik, tanda tanya, atau tanda seru.

Seperti yang ditunjukkan contoh berikut, saat kecocokan ditemukan, objek GroupCollection dan CaptureCollection diisi dengan pengambilan dari kecocokan. Dalam hal ini, grup (\w+[;,]?\s?) pengambilan ada sehingga + pembilang dapat diterapkan padanya, yang memungkinkan pola ekspresi reguler untuk mencocokkan setiap kata dalam kalimat. Jika tidak, itu akan cocok dengan kata terakhir dalam kalimat.

using System;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      string input = "This is one sentence. This is another.";
      string pattern = @"\b(\w+[;,]?\s?)+[.?!]";

      foreach (Match match in Regex.Matches(input, pattern)) {
         Console.WriteLine("Match: '{0}' at index {1}.",
                           match.Value, match.Index);
         int grpCtr = 0;
         foreach (Group grp in match.Groups) {
            Console.WriteLine("   Group {0}: '{1}' at index {2}.",
                              grpCtr, grp.Value, grp.Index);
            int capCtr = 0;
            foreach (Capture cap in grp.Captures) {
               Console.WriteLine("      Capture {0}: '{1}' at {2}.",
                                 capCtr, cap.Value, cap.Index);
               capCtr++;
            }
            grpCtr++;
         }
         Console.WriteLine();
      }
   }
}
// The example displays the following output:
//       Match: 'This is one sentence.' at index 0.
//          Group 0: 'This is one sentence.' at index 0.
//             Capture 0: 'This is one sentence.' at 0.
//          Group 1: 'sentence' at index 12.
//             Capture 0: 'This ' at 0.
//             Capture 1: 'is ' at 5.
//             Capture 2: 'one ' at 8.
//             Capture 3: 'sentence' at 12.
//
//       Match: 'This is another.' at index 22.
//          Group 0: 'This is another.' at index 22.
//             Capture 0: 'This is another.' at 22.
//          Group 1: 'another' at index 30.
//             Capture 0: 'This ' at 22.
//             Capture 1: 'is ' at 27.
//             Capture 2: 'another' at 30.
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim input As String = "This is one sentence. This is another."
        Dim pattern As String = "\b(\w+[;,]?\s?)+[.?!]"

        For Each match As Match In Regex.Matches(input, pattern)
            Console.WriteLine("Match: '{0}' at index {1}.",
                              match.Value, match.Index)
            Dim grpCtr As Integer = 0
            For Each grp As Group In match.Groups
                Console.WriteLine("   Group {0}: '{1}' at index {2}.",
                                  grpCtr, grp.Value, grp.Index)
                Dim capCtr As Integer = 0
                For Each cap As Capture In grp.Captures
                    Console.WriteLine("      Capture {0}: '{1}' at {2}.",
                                      capCtr, cap.Value, cap.Index)
                    capCtr += 1
                Next
                grpCtr += 1
            Next
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays the following output:
'       Match: 'This is one sentence.' at index 0.
'          Group 0: 'This is one sentence.' at index 0.
'             Capture 0: 'This is one sentence.' at 0.
'          Group 1: 'sentence' at index 12.
'             Capture 0: 'This ' at 0.
'             Capture 1: 'is ' at 5.
'             Capture 2: 'one ' at 8.
'             Capture 3: 'sentence' at 12.
'       
'       Match: 'This is another.' at index 22.
'          Group 0: 'This is another.' at index 22.
'             Capture 0: 'This is another.' at 22.
'          Group 1: 'another' at index 30.
'             Capture 0: 'This ' at 22.
'             Capture 1: 'is ' at 27.
'             Capture 2: 'another' at 30.

Saat Anda menggunakan subekspresi hanya untuk menerapkan kuantifier ke mereka dan Anda tidak tertarik dengan teks yang diambil, Anda harus menonaktifkan pengambilan grup. Misalnya, (?:subexpression) elemen bahasa mencegah grup yang diterapkannya untuk menangkap substring yang cocok. Dalam contoh berikut, pola ekspresi reguler dari contoh sebelumnya diubah menjadi \b(?:\w+[;,]?\s?)+[.?!]. Seperti yang ditunjukkan oleh output, ini mencegah mesin ekspresi reguler mengisi GroupCollection koleksi dan CaptureCollection :

using System;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      string input = "This is one sentence. This is another.";
      string pattern = @"\b(?:\w+[;,]?\s?)+[.?!]";

      foreach (Match match in Regex.Matches(input, pattern)) {
         Console.WriteLine("Match: '{0}' at index {1}.",
                           match.Value, match.Index);
         int grpCtr = 0;
         foreach (Group grp in match.Groups) {
            Console.WriteLine("   Group {0}: '{1}' at index {2}.",
                              grpCtr, grp.Value, grp.Index);
            int capCtr = 0;
            foreach (Capture cap in grp.Captures) {
               Console.WriteLine("      Capture {0}: '{1}' at {2}.",
                                 capCtr, cap.Value, cap.Index);
               capCtr++;
            }
            grpCtr++;
         }
         Console.WriteLine();
      }
   }
}
// The example displays the following output:
//       Match: 'This is one sentence.' at index 0.
//          Group 0: 'This is one sentence.' at index 0.
//             Capture 0: 'This is one sentence.' at 0.
//
//       Match: 'This is another.' at index 22.
//          Group 0: 'This is another.' at index 22.
//             Capture 0: 'This is another.' at 22.
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim input As String = "This is one sentence. This is another."
        Dim pattern As String = "\b(?:\w+[;,]?\s?)+[.?!]"

        For Each match As Match In Regex.Matches(input, pattern)
            Console.WriteLine("Match: '{0}' at index {1}.",
                              match.Value, match.Index)
            Dim grpCtr As Integer = 0
            For Each grp As Group In match.Groups
                Console.WriteLine("   Group {0}: '{1}' at index {2}.",
                                  grpCtr, grp.Value, grp.Index)
                Dim capCtr As Integer = 0
                For Each cap As Capture In grp.Captures
                    Console.WriteLine("      Capture {0}: '{1}' at {2}.",
                                      capCtr, cap.Value, cap.Index)
                    capCtr += 1
                Next
                grpCtr += 1
            Next
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays the following output:
'       Match: 'This is one sentence.' at index 0.
'          Group 0: 'This is one sentence.' at index 0.
'             Capture 0: 'This is one sentence.' at 0.
'       
'       Match: 'This is another.' at index 22.
'          Group 0: 'This is another.' at index 22.
'             Capture 0: 'This is another.' at 22.

Anda dapat melakukannya dengan salah satu cara berikut:

  • Gunakan (?:subexpression) elemen bahasa. Elemen ini mencegah pengambilan substring yang cocok dalam grup yang berlaku. Ini tidak menonaktifkan pengambilan substring di grup berlapis apa pun.

  • Gunakan ExplicitCapture opsi tersebut. Ini menonaktifkan semua pengambilan yang tidak disebutkan namanya atau implisit dalam pola ekspresi reguler. Saat Anda menggunakan opsi ini, hanya substring yang cocok dengan grup bernama yang ditentukan dengan (?<name>subexpression) elemen bahasa yang dapat diambil. ExplicitCapture Bendera dapat diteruskan ke options parameter Regex konstruktor kelas atau ke options parameter Regex metode pencocokan statis.

  • Gunakan n opsi dalam (?imnsx) elemen bahasa. Opsi ini menonaktifkan semua pengambilan yang tidak disebutkan namanya atau implisit dari titik dalam pola ekspresi reguler ketika elemen muncul. Pengambilan dinonaktifkan baik hingga akhir pola atau hingga (-n) opsi mengaktifkan pengambilan yang tidak disebutkan namanya atau implisit. Untuk informasi selengkapnya, lihat Konstruksi Lain-Lain.

  • Gunakan n opsi dalam (?imnsx:subexpression) elemen bahasa. Opsi ini menonaktifkan semua pengambilan yang tidak disebutkan namanya atau implisit di subexpression. Pengambilan oleh grup pengambilan bertumpuk yang tidak disebutkan namanya atau implisit juga dinonaktifkan.

Judul Deskripsi
Detail Perilaku Ekspresi Reguler Memeriksa implementasi mesin ekspresi reguler di .NET. Artikel ini berfokus pada fleksibilitas ekspresi reguler dan menjelaskan tanggung jawab pengembang untuk memastikan operasi mesin ekspresi reguler yang efisien dan kuat.
Pelacakan Balik Menjelaskan apa itu backtracking dan bagaimana hal itu mempengaruhi kinerja ekspresi reguler, dan memeriksa elemen bahasa yang memberikan alternatif untuk mundur.
Bahasa Regex - Referensi Cepat Menjelaskan elemen bahasa ekspresi reguler di .NET dan menyediakan tautan ke dokumentasi terperinci untuk setiap elemen bahasa.