Osvědčené postupy pro regulární výrazy v .NET

Modul regulárních výrazů v .NET je výkonný plnohodnotný nástroj, který zpracovává text na základě porovnávání vzorů místo porovnání a porovnávání literálového textu. Ve většině případů provádí porovnání vzorů rychle a efektivně. V některých případech se ale může zdát, že modul regulárních výrazů je pomalý. V extrémních případech se může dokonce zdát, že přestal při zpracování relativně malého vstupu odpovídat po dobu hodin nebo dokonce dní.

Tento článek popisuje některé z osvědčených postupů, které mohou vývojáři přijmout, aby zajistili optimální výkon jejich regulárních výrazů.

Upozorňující

Při zpracování System.Text.RegularExpressions nedůvěryhodného vstupu předejte vypršení časového limitu. Uživatel se zlými úmysly může poskytnout vstup RegularExpressions, což způsobí útok na dostupnost služby. ASP.NET rozhraní API architektury Core, která používají RegularExpressions vypršení časového limitu.

Zvažte vstupní zdroj.

Regulární výrazy mohou většinou přijmout dva typy vstupů: vstupy s omezením a vstupy bez omezení. Omezený vstup je text, který pochází ze známého nebo spolehlivého zdroje a řídí se předdefinovaným formátem. Nezatrénovaný vstup je text, který pochází z nespolehlivého zdroje, například webového uživatele, a nemusí následovat předdefinovaný nebo očekávaný formát.

Vzory regulárních výrazů se často zapisují tak, aby odpovídaly platnému vstupu. Vývojáři totiž prozkoumají text, který chtějí porovnat, a následně napíšou vzor regulárního výrazu, který mu odpovídá. Vývojáři následně určí, zda tento vzor vyžaduje korekci nebo další zpracování testováním pomocí většího množství platných vstupních položek. Když vzor odpovídá všem předpokládaným platným vstupům, je deklarován jako připravený pro produkční prostředí a může být součástí vydané aplikace. Tento přístup vytvoří vzor regulárního výrazu vhodný pro odpovídající omezený vstup. Nepřipadá ale vhod pro odpovídající nekontrénovaný vstup.

Aby bylo možné spárovat nezařazený vstup, musí regulární výraz efektivně zpracovávat tři druhy textu:

  • Text, který odpovídá vzoru regulárního výrazu.
  • Text, který neodpovídá vzoru regulárního výrazu
  • Text, který téměř odpovídá vzoru regulárního výrazu.

Poslední typ je zvláště problematický pro regulární výrazy, které jsou napsány tak, aby zpracovávaly vstup s omezením. Pokud se tento regulární výraz také spoléhá na rozsáhlé navracení, modul regulárních výrazů může strávit nesrozenou dobu (v některých případech, mnoho hodin nebo dnů) zpracování zdánlivě nečaseného textu.

Upozorňující

Následující příklad používá regulární výraz, který je náchylný k nadměrnému navracení a který pravděpodobně odmítne platné e-mailové adresy. Neměli byste ho používat v rutině ověřování e-mailu. Pokud chcete regulární výraz, který ověřuje e-mailové adresy, přečtěte si článek Postupy: Ověření, že řetězce jsou v platném formátu e-mailu.

Představte si například běžně používaný, ale problematický regulární výraz pro ověřování aliasu e-mailové adresy. Regulární výraz ^[0-9A-Z]([-.\w]*[0-9A-Z])*$ se zapíše ke zpracování toho, co se považuje za platnou e-mailovou adresu. Platná e-mailová adresa se skládá z alfanumerického znaku následovaného nulou nebo více znaků, které můžou být alfanumerické, tečky nebo pomlčky. Regulární výraz musí končit alfanumerickým znakem. Jak ale ukazuje následující příklad, i když tento regulární výraz zpracovává platný vstup snadno, jeho výkon je neefektivní, když zpracovává téměř platný vstup:

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

Jak ukazuje výstup z předchozího příkladu, modul regulárních výrazů zpracuje platný e-mailový alias přibližně ve stejném časovém intervalu bez ohledu na jeho délku. Na druhou stranu platí, že pokud má téměř platná e-mailová adresa více než pět znaků, doba zpracování se přibližně zdvojnásobí pro každý nadbytečný znak v řetězci. Zpracování téměř platného řetězce o 28 znaménce tedy trvá déle než hodinu a téměř platný řetězec o 33 znaménce bude trvat téměř den, než se zpracuje.

Vzhledem k tomu, že tento regulární výraz byl vyvinut výhradně vzhledem k formátu vstupu, který se má shodovat, nezohledňuje vstup, který neodpovídá vzoru. Tento dohled může zase umožnit nekontrénovaný vstup, který téměř odpovídá vzoru regulárního výrazu, aby výrazně snížil výkon.

Chcete-li tento problém vyřešit, proveďte následující akce:

  • Při vytváření vzoru je třeba zvážit, jakým způsobem mechanismus zpětného navracení může ovlivnit výkon modulu regulárních výrazů, především tehdy, pokud je regulární výraz navržen pro zpracování vstupu bez omezení. Další informace najdete v části Převzetí zpětného navracení .

  • Důkladně otestujte regulární výraz pomocí neplatného, téměř platného a platného vstupu. Rex můžete použít k náhodnému vygenerování vstupu pro konkrétní regulární výraz. Rex je nástroj pro zkoumání regulárních výrazů od Microsoft Research.

Správně zpracovat instanci objektu

V srdci . Objektový model regulárního výrazu System.Text.RegularExpressions.Regex net je třída, která představuje modul regulárních výrazů. Jedním z největších faktorů, který ovlivňuje výkon regulárního výrazu, je často způsob, jakým Regex se modul používá. Definování regulárního výrazu zahrnuje pevné párování modulu regulárních výrazů se vzorem regulárního výrazu. Tento proces párování, ať už zahrnuje vytvoření instance Regex objektu předáním jeho konstruktoru vzor regulárního výrazu nebo voláním statické metody předáním vzoru regulárního výrazu a řetězce, který se má analyzovat, je nutností nákladný.

Poznámka:

Podrobnou diskuzi o dopadu na výkon při použití interpretovaných a zkompilovaných regulárních výrazů naleznete v tématu Optimalizace výkonu regulárních výrazů, část II: Převzetí zpětného navracení v blogu týmu BCL.

Modul regulárních výrazů můžete spárovat s konkrétním vzorem regulárního výrazu a pak ho použít k páru textu několika způsoby:

  • Můžete volat statickou metodu porovnávání vzorů, například Regex.Match(String, String). Tato metoda nevyžaduje vytvoření instance objektu regulárního výrazu.

  • Můžete vytvořit instanci objektu a volat metodu Regex porovnávání vzorů instance interpretovaného regulárního výrazu, což je výchozí metoda pro vazbu modulu regulárních výrazů na vzor regulárního výrazu. Regex Výsledkem je vytvoření instance objektu bez argumentu optionsCompiled, který obsahuje příznak.

  • Můžete vytvořit instanci objektu Regex a volat metodu porovnávání vzorů instance zkompilovaného regulárního výrazu. Objekty regulárního výrazu představují kompilované vzory při Regex vytvoření instance objektu s argumentem options , který obsahuje Compiled příznak.

  • Můžete vytvořit objekt pro zvláštní účely Regex , který je úzce propojený s konkrétním vzorem regulárního výrazu, zkompilovat ho a uložit do samostatného sestavení. Můžete volat metodu Regex.CompileToAssembly , která se má zkompilovat a uložit.

Konkrétní způsob volání metod porovnávání regulárních výrazů může ovlivnit výkon vaší aplikace. V následujících částech jsou probírány způsoby volání statických metod, interpretovaných regulárních výrazů a zkompilovaných regulárních výrazů za účelem zvýšení výkonu aplikace.

Důležité

Způsob volání metod (statické, interpretované, zkompilované) ovlivňuje výkon, pokud pro volání metod použijete stejný regulární výraz, nebo pokud aplikace příliš často používá objekty regulárních výrazů.

Statické regulární výrazy

Statické metody regulárních výrazů jsou vhodnou alternativou k opakovanému vytváření instancí objektů regulárních výrazů se stejným regulárním výrazem. Na rozdíl od vzorů regulárních výrazů používaných objekty regulárních výrazů se modul regulárních výrazů interně ukládá do mezipaměti kódy operací nebo zkompilovaný společný zprostředkující jazyk (CIL) ze vzorů používaných při volání statické metody do mezipaměti.

Například pro ověření uživatelského vstupu volá obslužná rutina události často jinou metodu. Tento příklad se odráží v následujícím kódu, ve kterém Button se událost ovládacího prvku Click používá k volání metody pojmenované IsValidCurrency, která kontroluje, zda uživatel zadal symbol měny následovaný alespoň jednou desetinnou číslicí.

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

Neefektivní implementace IsValidCurrency metody je znázorněna v následujícím příkladu:

Poznámka:

Každé volání metody znovu vytvoří Regex objekt se stejným vzorem. To znamená, že vzor regulárního výrazu musí být při každém dalším volání metody znovu zkompilován.

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

Předchozí neefektivní kód byste měli nahradit voláním statické Regex.IsMatch(String, String) metody. Tento přístup eliminuje nutnost vytvořit instanci objektu Regex pokaždé, když chcete volat metodu porovnávání vzorů, a umožňuje modulu regulárních výrazů načíst zkompilovanou verzi regulárního výrazu z mezipaměti.

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

Ve výchozím nastavení je v mezipaměti uloženo posledních 15 použitých vzorů regulárních výrazů. U aplikací, které vyžadují větší počet statických regulárních výrazů uložených v mezipaměti, lze velikost mezipaměti upravit nastavením Regex.CacheSize vlastnosti.

Regulární výraz \p{Sc}+\s*\d+ použitý v tomto příkladu ověřuje, že vstupní řetězec má symbol měny a alespoň jednu desetinnou číslici. Vzor je definován, jak je znázorněno v následující tabulce:

Vzor Popis
\p{Sc}+ Odpovídá jednomu nebo více znakům v kategorii Symbol Unicode, Měna.
\s* Odpovídá nule nebo více prázdných znaků.
\d+ Odpovídá jedné nebo více desítkových číslic.

Interpretované vs. kompilované regulární výrazy

Vzory regulárních výrazů, které nejsou vázané na modul regulárních Compiled výrazů prostřednictvím specifikace možnosti, se interpretují. Po vytvoření instance objektu regulárního výrazu převede modul regulárních výrazů regulární výraz na množinu kódů operací. Při zavolání metody instance se kódy operací převedou na CIL a spustí kompilátor JIT. Podobně platí, že pokud je volána statická metoda regulárního výrazu a regulární výraz nelze najít v mezipaměti, modul regulárních výrazů převede regulární výraz na sadu kódů operací a uloží je do mezipaměti. Potom tyto kódy operací převede na CIL, aby je kompilátor JIT mohl spustit. U interpretovaných regulárních výrazů je čas spuštění zkrácen na úkor delšího času provádění. Kvůli tomuto procesu se nejlépe používají, když se regulární výraz používá v malém počtu volání metody, nebo pokud přesný počet volání metod regulárního výrazu je neznámý, ale očekává se, že bude malý. S rostoucím počtem volání metod je výkonový zisk plynoucí z kratší doby spouštění kompenzován nižší rychlostí provádění.

Vzory regulárních výrazů vázané na modul regulárních Compiled výrazů prostřednictvím specifikace možnosti jsou zkompilovány. Proto při vytvoření instance objektu regulárního výrazu nebo při zavolání statické metody regulárního výrazu a regulární výraz nelze nalézt v mezipaměti, modul regulárních výrazů převede regulární výraz na zprostředkující sadu kódů operací. Tyto kódy se pak převedou na CIL. Když je volána metoda, kompilátor JIT spustí CIL. Na rozdíl od interpretovaných regulárních výrazů mají zkompilované regulární výrazy delší dobu spouštění, jednotlivé metody porovnávání se však provádí rychleji. Ve výsledku se výkonový zisk plynoucí z kompilování regulárního výrazu zvyšuje s počtem regulárních výrazů, které metoda volá.

Shrnutí: V případě, že metody regulárních výrazů s konkrétním regulárním výrazem nebudou volány tak často, doporučujeme vám, abyste použili interpretované regulární výrazy. Pokud metody regulárních výrazů s konkrétním regulárním výrazem voláte relativně často, měli byste použít zkompilované regulární výrazy. Je obtížné určit přesnou prahovou hodnotu, při které nižší rychlost provádění interpretovaných regulárních výrazů převáží zisky z kratší doby spuštění, nebo prahovou hodnotu, při které pomalejší časy spouštění zkompilovaných regulárních výrazů převáží zisky z jejich rychlejší rychlosti provádění. Závisí na různých faktorech, včetně složitosti regulárního výrazu a konkrétních dat, která zpracovává. Pokud chcete zjistit, jestli interpretované nebo kompilované regulární výrazy nabízejí nejlepší výkon pro váš konkrétní scénář aplikace, můžete třídu použít Stopwatch k porovnání jejich časů provádění.

Následující příklad porovnává výkon zkompilovaných a interpretovaných regulárních výrazů při čtení prvních 10 vět a při čtení všech vět v textu The Financier The Financier The TheDreiser. Jak ukazuje výstup z příkladu, při pouhých 10 voláních metod porovnávání regulárních výrazů nabízí interpretovaný regulární výraz lepší výkon než zkompilovaný regulární výraz. Zkompilovaný regulární výraz však nabízí lepší výkon, pokud je proveden větší počet volání (v tomto případě 13 000).

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

Vzor regulárního výrazu použitý v příkladu \b(\w+((\r?\n)|,?\s))*\w+[.?:;!]je definován, jak je znázorněno v následující tabulce:

Vzor Popis
\b Začne porovnání na hranici slova.
\w+ Odpovídá jednomu nebo více znakům slova.
(\r?\n)|,?\s) Odpovídá nule nebo jednomu návratu na začátek řádku, za nímž následuje znak nového řádku, nebo nula nebo jedna čárka následovaná prázdným znakem.
(\w+((\r?\n)|,?\s))* Odpovídá nule nebo více výskytů jednoho nebo více znaků slova, za kterými následuje nula nebo jeden znak řádku a znak nového řádku, nebo nulou nebo jednou čárkou následovanou prázdným znakem.
\w+ Odpovídá jednomu nebo více znakům slova.
[.?:;!] Odpovídá tečkě, otazníku, dvojtečku, středníku nebo vykřičníku.

Regulární výrazy: Kompilováno do sestavení

.NET také umožňuje vytvořit sestavení, které obsahuje zkompilované regulární výrazy. Tato funkce přesune výkon kompilace regulárních výrazů z doby běhu na čas návrhu. Zahrnuje ale také další práci. Regulární výrazy je nutné definovat předem a zkompilovat je do sestavení. Kompilátor pak může na toto sestavení odkazovat při kompilaci zdrojového kódu, který používá regulární výrazy sestavení. Každý zkompilovaný regulární výraz v sestavení je reprezentován třídou, která je odvozena z Regex.

Chcete-li kompilovat regulární výrazy do sestavení, zavoláte metodu Regex.CompileToAssembly(RegexCompilationInfo[], AssemblyName) a předáte jí pole RegexCompilationInfo objektů a objektu AssemblyName . Objekty RegexCompilationInfo představují regulární výrazy, které se mají zkompilovat, a AssemblyName objekt, který obsahuje informace o sestavení, které se má vytvořit.

Kompilaci regulárních výrazů do sestavení doporučujeme provádět v následujících situacích:

  • Pokud jste vývojář komponent, který chce vytvořit knihovnu opakovaně použitelných regulárních výrazů.
  • Pokud očekáváte, že metody porovnávání vzorů regulárního výrazu budou volány neurčitým počtem opakování – kdekoli od jednoho nebo dvakrát do tisíců nebo desítek tisíckrát. Na rozdíl od kompilovaných nebo interpretovaných regulárních výrazů nabízejí regulární výrazy kompilované do samostatných sestavení výkon, který je konzistentní bez ohledu na počet volání metody.

Pokud k optimalizaci výkonu používáte kompilované regulární výrazy, neměli byste k vytvoření sestavení použít reflexi, načíst modul regulárních výrazů a spustit jeho metody porovnávání vzorů. Abyste se vyhnuli reflexi, nemusíte dynamicky sestavovat vzory regulárních výrazů a zadávat všechny možnosti porovnávání vzorů, jako je porovnávání vzorů bez rozlišování malých a velkých písmen, při vytváření sestavení. Vyžaduje také oddělení kódu, který vytváří sestavení, od kódu, který používá regulární výraz.

Následující příklad znázorňuje způsob vytvoření sestavení, které obsahuje zkompilovaný regulární výraz. Vytvoří sestavení pojmenované RegexLib.dll s jednou třídou regulárního výrazu , SentencePattern. Tato třída obsahuje vzor regulárního výrazu odpovídající větám, který se používá v části Interpreted vs. Zkompilované regulární výrazy .

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

Když je příklad zkompilován do spustitelného souboru a spuštěn, vytvoří sestavení s názvem RegexLib.dll. Třída Utilities.RegularExpressions.SentencePattern odvozená z Regex představuje regulární výraz. Následující příklad pak použije zkompilovaný regulární výraz k extrakci vět z textu Theodore Dreiser's The Financier:

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.

Převzetí poplatků za navracení

Pro posouvání ve vstupním řetězci a porovnání řetězce se vzorem regulárního výrazu používá regulární výraz většinou lineární posloupnost. Pokud jsou však neurčité kvantifikátory, jako *je například , +a ? používají se ve vzoru regulárního výrazu, modul regulárních výrazů může vzdát část úspěšných částečných shod a vrátit se do dříve uloženého stavu, aby bylo možné vyhledat úspěšnou shodu pro celý vzor. Tento proces se označuje jako zpětné navracení.

Tip

Další informace o navracení najdete v tématu Podrobnosti o chování regulárních výrazů a navracení. Podrobné diskuze o navracení najdete v blogových příspěvcích o vylepšení regulárních výrazů v .NET 7 a optimalizaci výkonu regulárních výrazů.

Podpora zpětného navracení zajišťuje regulárním výrazům výkon a flexibilitu. Zároveň přenáší odpovědnost za řízení provozu modulu regulárních výrazů do rukou vývojáře regulárních výrazů. Vzhledem k tomu, že vývojáři si často tuto odpovědnost neuvědomují, jejich špatný způsob používání mechanismu navracení nebo přílišné používání tohoto mechanismu hraje nejdůležitější roli při snížení výkonu regulárních výrazů. V nejhorším případě se doba provádění může s každým dalším znakem ve vstupním řetězci zdvojnásobit. Ve skutečnosti je použití zpětného navracení příliš snadné vytvořit programový ekvivalent nekonečné smyčky, pokud vstup téměř odpovídá vzoru regulárního výrazu. Modul regulárních výrazů může zpracování relativně krátkého vstupního řetězce trvat hodiny nebo dokonce dny.

Aplikace často platí za používání navracení výkonu, i když zpětné navracení není pro shodu nezbytné. Regulární výraz \b\p{Lu}\w*\b například odpovídá všem slovem, která začínají velkými písmeny, jak ukazuje následující tabulka:

Vzor Popis
\b Začne porovnání na hranici slova.
\p{Lu} Odpovídá velkým písmenu.
\w* Odpovídá nule nebo více znaků slova.
\b Ukončí porovnání na hranici slova.

Vzhledem k tomu, že hranice slova není stejná jako podmnožina znaku slova, neexistuje možnost, že modul regulárních výrazů při porovnávání znaků slova překročí hranici slova. Proto u tohoto regulárního výrazu nemůže zpětné navracení nikdy přispět k celkovému úspěchu jakékoli shody. Může snížit výkon, protože modul regulárních výrazů je nucen uložit svůj stav pro každou úspěšnou předběžnou shodu znaku slova.

Pokud zjistíte, že zpětné navracení není nutné, můžete ho zakázat několika způsoby:

  • RegexOptions.NonBacktracking Nastavením možnosti (představené v .NET 7) Další informace naleznete v tématu Režim zpětného navracení.

  • Pomocí elementu (?>subexpression) jazyka, označovaného jako atomická skupina. Následující příklad analyzuje vstupní řetězec pomocí dvou regulárních výrazů. První , \b\p{Lu}\w*\bspoléhá na navracení. Druhý, \b\p{Lu}(?>\w*)\bzakáže navracení. Jak ukazuje výstup z příkladu, oba vytvoří stejný výsledek:

    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
    

V mnoha případech je zpětné navracení pro porovnávání vzorů regulárních výrazů se vstupním textem nezbytné. Přílišné používání mechanismu navracení však může zásadním způsobem snížit výkon a vytvořit dojem, že aplikace přestala odpovídat. Konkrétně k tomuto problému dochází, když jsou kvantifikátory vnořené a text, který odpovídá vnějšímu dílčímu výrazu, je podmnožinou textu, která odpovídá vnitřnímu dílčímu výrazu.

Upozorňující

Kromě toho, abyste se vyhnuli nadměrnému navracení, měli byste použít funkci časového limitu, abyste zajistili, že nadměrné navracení nezpůsobí vážné snížení výkonu regulárních výrazů. Další informace najdete v části Použití hodnot časového limitu.

Vzor regulárního výrazu ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$ je například určený ke shodě čísla části, která se skládá z alespoň jednoho alfanumerického znaku. Mezi dalšími znaky může být alfanumerický znak, spojovník, podtržítko nebo tečka, poslední znak však musí být alfanumerický. Znak dolaru ukončuje číslo součásti. V některých případech může tento vzor regulárního výrazu vykazovat nízký výkon, protože kvantifikátory jsou vnořené a protože dílčí výraz [0-9A-Z] je podmnožinou dílčího výrazu [-.\w]*.

V těchto případech lze výkon regulárních výrazů optimalizovat odebráním vnořených kvantifikátorů a nahrazením vnějšího dílčího výrazu kontrolním výrazem dopředného nebo zpětného vyhledávání s nulovou šířkou. Kontrolní výrazy lookbehind a lookbehind jsou kotvy. Nepřesouvají ukazatel ve vstupním řetězci, ale místo toho se dívají dopředu nebo za a zkontrolují, jestli je zadaná podmínka splněná. Například regulární výraz čísla části lze přepsat jako ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$. Tento vzor regulárního výrazu je definován, jak je znázorněno v následující tabulce:

Vzor Popis
^ Zahájí porovnávání na začátku vstupního řetězce.
[0-9A-Z] Porovná alfanumerický znak. Číslo součásti musí obsahovat alespoň tento znak.
[-.\w]* Porovná žádný nebo více výskytů znaku slova, spojovníku nebo tečky.
\$ Porovná znak dolaru.
(?<=[0-9A-Z]) Podívejte se na koncové znaménko dolaru a ujistěte se, že předchozí znak je alfanumerický.
$ Ukončí porovnávání na konci vstupního řetězce.

Následující příklad ukazuje použití tohoto regulárního výrazu k porovnání pole obsahujícího možná čísla částí:

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.

Jazyk regulárního výrazu v .NET obsahuje následující prvky jazyka, které můžete použít k odstranění vnořených kvantifikátorů. Další informace naleznete v tématu Seskupování konstruktorů.

Prvek jazyka Popis
(?= subexpression ) Pozitivní dopředné vyhledávání s nulovou šířkou. Před aktuální pozicí určí, jestli subexpression odpovídá vstupnímu řetězci.
(?! subexpression ) Negativní dopředné vyhledávání s nulovou šířkou. Hledá dopředu aktuální pozici, abyste zjistili, jestli subexpression neodpovídá vstupnímu řetězci.
(?<= subexpression ) Pozitivní zpětné vyhledávání s nulovou šířkou. Vyhledá aktuální pozici a určí, jestli subexpression odpovídá vstupnímu řetězci.
(?<! subexpression ) Negativní zpětné vyhledávání s nulovou šířkou. Vyhledá aktuální pozici a určí, jestli subexpression neodpovídá vstupnímu řetězci.

Použití hodnot časového limitu

Pokud regulární výraz zpracovává vstup, který téměř odpovídá vzoru regulárního výrazu, může se často spoléhat na přílišné používání zpětného navracení, což má velký dopad na výkon. Kromě pečlivého zvážení použití zpětného navracení a testování regulárního výrazu proti téměř shodnému vstupu byste měli vždy nastavit hodnotu časového limitu, abyste minimalizovali účinek nadměrného navracení, pokud dojde k jeho výskytu.

Interval časového limitu regulárního výrazu definuje časové období, po které bude modul regulárních výrazů hledat jednu shodu před vypršením časového limitu. V závislosti na vzoru regulárního výrazu a vstupním textu může doba provádění překročit zadaný interval časového limitu, ale nebude trávit více času navracením, než je zadaný interval časového limitu. Výchozí interval časového limitu je Regex.InfiniteMatchTimeout, což znamená, že regulární výraz nevysadí časový limit. Tuto hodnotu můžete přepsat a definovat interval časového limitu následujícím způsobem:

Pokud jste definovali časový limit a na konci tohoto intervalu se nenajde shoda, vyvolá metoda regulárního výrazu RegexMatchTimeoutException výjimku. V obslužné rutině výjimky můžete zkusit opakovat shodu s delším intervalem časového limitu, opustit pokus o shodu a předpokládat, že neexistuje žádná shoda, nebo opustit pokus o shodu a protokolovat informace o výjimce pro budoucí analýzu.

Následující příklad definuje metodu GetWordData , která vytvoří instanci regulárního výrazu s časovým limitem 350 milisekund k výpočtu počtu slov a průměrného počtu znaků ve slově v textovém dokumentu. Pokud vyprší časový limit odpovídající operace, interval časového limitu se zvýší o 350 milisekund a Regex objekt se znovu spustí. Pokud nový interval časového limitu překročí jednu sekundu, metoda znovu zvětší výjimku volajícího.

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

Zachytávání pouze v případě potřeby

Regulární výrazy v .NET podporují seskupování konstruktorů, které umožňují seskupit vzor regulárního výrazu do jednoho nebo více dílčích výrazů. Nejčastěji používané konstrukty seskupování v jazyce regulárních výrazů .NET jsou (dílčí výrazy), které definují číslované zachytávané skupiny a(?< podvýraz) názvů>, který definuje pojmenovanou skupinu zachytávání. Seskupovací konstrukce jsou nezbytné pro vytváření a definování dílčího výrazu, pro který je použit kvantifikátor.

Používání těchto prvků jazyka však má svou cenu. GroupCollection Způsobí, že objekt vrácený Match.Groups vlastností se naplní nejnovějšími nepojmenovanými nebo pojmenovanými zachyceními. Pokud jeden konstruktor seskupení zachytil více podřetězců ve vstupním řetězci, naplní také CaptureCollection objekt vrácený Group.Captures vlastností konkrétní zachytávání skupin více Capture objektů.

Konstrukty seskupení se často používají pouze v regulárním výrazu, aby na ně bylo možné použít kvantifikátory. Skupiny zachycené těmito dílčími výrazy se později nepoužívají. Regulární výraz \b(\w+[;,]?\s?)+[.?!] je například navržený tak, aby zachytil celou větu. Následující tabulka popisuje prvky jazyka v tomto vzoru regulárního výrazu a jejich vliv na Match objekty Match.Groups a Group.Captures kolekce:

Vzor Popis
\b Začne porovnání na hranici slova.
\w+ Odpovídá jednomu nebo více znakům slova.
[;,]? Odpovídá nule nebo jedné čárkě nebo středníku.
\s? Odpovídá nulovému nebo jednomu prázdnému znaku.
(\w+[;,]?\s?)+ Odpovídá jednomu nebo více výskytům jednoho nebo více znaků slova následovaných nepovinným čárkou nebo středníkem následovaným volitelným prázdným znakem. Tento vzor definuje první zachycenou skupinu, což je nezbytné, aby kombinace více znaků slova (tj. slova) následovaná volitelným interpunkčním znakem se opakovala, dokud modul regulárních výrazů nedosáhne konce věty.
[.?!] Odpovídá tečkě, otazníku nebo vykřičníku.

Jak ukazuje následující příklad, když je nalezena shoda, jsou oba GroupCollection objekty CaptureCollection naplněny zachycením z shody. V tomto případě existuje zachytávací skupina (\w+[;,]?\s?) , aby + na ni bylo možné použít kvantifikátor, který umožňuje vzor regulárního výrazu odpovídat jednotlivým slovům ve větě. V opačném případě může odpovídat poslednímu slovu ve větě.

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.

Pokud použijete dílčí výrazy pouze k použití kvantifikátorů a zachycený text vás nezajímá, měli byste zakázat zachytávání skupin. Například prvek jazyka zabraňuje skupině, (?:subexpression) na kterou se vztahuje, aby zachytil odpovídající podřetětěc. V následujícím příkladu se vzor regulárního výrazu z předchozího příkladu změní na \b(?:\w+[;,]?\s?)+[.?!]. Jak ukazuje výstup, zabrání modulu regulárních výrazů v naplnění GroupCollection kolekcí a CaptureCollection kolekcí:

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.

Zachytávání lze zakázat jedním z následujících způsobů:

  • (?:subexpression) Použijte prvek jazyka. Tento prvek zabraňuje v zachytávání shodných podřetězců ve skupině, pro kterou se používá. Nezakazuje zachycení podřetězců v žádné vnořené skupiny.

  • Použijte tuto ExplicitCapture možnost. Zakazuje všechna nepojmenovaná nebo implicitní zachycení ve vzoru regulárního výrazu. Při použití této možnosti lze zachytit pouze podřetětěce, které odpovídají pojmenovaným skupinám definovaným s elementem (?<name>subexpression) jazyka. Příznak ExplicitCapture lze předat options parametru konstruktoru Regex třídy nebo options parametru Regex statické odpovídající metody.

  • n Použijte možnost v elementu (?imnsx) jazyka. Tato možnost zakazuje všechna nepojmenovaná nebo implicitní zachycení z bodu ve vzoru regulárního výrazu, ve kterém se prvek objeví. Zachytávání se zakáže až do konce vzoru nebo dokud (-n) možnost nepovolí nepojmenované nebo implicitní zachycení. Další informace naleznete v tématu Různé konstrukce.

  • n Použijte možnost v elementu (?imnsx:subexpression) jazyka. Tato možnost zakáže všechny nepojmenované nebo implicitní zachycení v subexpression. Zachycení nepojmenovanou nebo implicitní vnořenou zachytávající skupinou jsou rovněž zakázána.

Titulek Popis
Podrobnosti k chování regulárních výrazů Prozkoumá implementaci modulu regulárních výrazů v .NET. Článek se zaměřuje na flexibilitu regulárních výrazů a vysvětluje odpovědnost vývojáře za zajištění efektivního a robustního provozu modulu regulárních výrazů.
Zpětné navracení Vysvětluje princip zpětného navracení a vliv tohoto mechanismu na výkon regulárních výrazů a zkoumá prvky jazyka, které nabízí alternativu zpětného navracení.
Jazyk regulárních výrazů – stručná referenční dokumentace Popisuje prvky jazyka regulárních výrazů v .NET a poskytuje odkazy na podrobnou dokumentaci pro každý prvek jazyka.