Sdílet prostřednictvím


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 DesignExample
{
    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í je nákladný, ať už zahrnuje vytvoření instance Regex objektu předáním jeho konstruktoru vzoru 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.

Poznámka:

Podrobnou diskuzi o dopadu na výkon při použití interpretovaných a kompilovaných regulárních výrazů najdete v blogovém příspěvku Optimalizace výkonu regulárních výrazů, část II: Převzetí zpětného navracení.

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 options Compiled, který obsahuje příznak.

  • Můžete vytvořit instanci objektu Regex a volat metodu porovnávání vzorů instance zdrojového regulárního výrazu. Tato technika se ve většině případů doporučuje. Uděláte to tak, že atribut umístíte GeneratedRegexAttribute na částečnou metodu, která vrátí Regex.

  • 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.

Konkrétní způsob volání metod porovnávání regulárních výrazů může ovlivnit výkon vaší aplikace. Následující části diskutují, kdy použít volání statických metod, zdrojové generované regulární výrazy, interpretované regulární výrazy a zkompilované regulární výrazy ke zlepšení výkonu vaší aplikace.

Důležité

Forma volání metody (statické, interpretované, generované, kompilované) ovlivňuje výkon, pokud se stejný regulární výraz používá opakovaně ve volání metody nebo pokud aplikace používá rozsáhlé použití objektů 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 kódy operací (opcodes) nebo kompilovaný běžný zprostředkující jazyk (CIL) ze vzorů používaných při volání statické metody interně ukládá modul regulárních výrazů 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 RegexLib2
{
   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. zdrojové 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á.

Vzory regulárních výrazů vázané na modul regulárních výrazů prostřednictvím doplňku RegEx-returning metody s atributem GeneratedRegexAttribute jsou generovány. Zdrojový generátor, který se připojí k kompilátoru, generuje jako kód jazyka C# vlastní Regex-odvozenou implementaci s logikou podobnou tomu, co RegexOptions.Compiled generuje v CIL. Získáte všechny výhody RegexOptions.Compiled výkonu propustnosti (ve skutečnosti) a výhody Regex.CompileToAssemblyspuštění , ale bez složitosti CompileToAssembly. Zdroj, který se vygeneruje, je součástí projektu, což znamená, že je také snadno zobrazitelný a laditelný.

Pokud chcete shrnout, doporučujeme:

  • Interpretované regulární výrazy se používají při volání metod regulárních výrazů s konkrétním regulárním výrazem relativně zřídka.
  • Regulární výrazy generované zdrojem použijte, pokud používáte Regex v jazyce C# s argumenty známými v době kompilace a používáte konkrétní regulární výraz relativně často.
  • Kompilované regulární výrazy používejte při volání metod regulárních výrazů s určitým regulárním výrazem relativně často a používáte .NET 6 nebo starší verzi.

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í. Je také obtížné určit prahovou hodnotu, při které pomalejší doby spouštění zdrojových nebo kompilovaných regulárních výrazů převáží zisky z jejich rychlejších rychlostí spouštění. Prahové hodnoty závisí na různých faktorech, včetně složitosti regulárního výrazu a konkrétních dat, která zpracovává. Chcete-li určit, které regulární výrazy nabízejí nejlepší výkon pro váš konkrétní scénář aplikace, můžete použít Stopwatch třídu k porovnání jejich časů provádění.

Následující příklad porovnává výkon zkompilovaných, zdrojový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 Theodore Dreisera Financier. Jak ukazuje výstup z příkladu, když se provádí pouze 10 volání metod porovnávání regulárních výrazů, interpretovaný nebo zdrojový regulární výraz nabízí 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).

const string pattern = @"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]";

[GeneratedRegex(pattern, RegexOptions.Singleline)]
private static partial Regex GeneratedRegex();

public static void RunIt()
{
    Stopwatch sw;
    Match match;
    int ctr;

    StreamReader inFile = new(@".\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(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 first ten sentences with source-generated regex.
    Console.WriteLine("10 Sentences with Source-generated Regex:");
    sw = Stopwatch.StartNew();

    match = GeneratedRegex().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(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(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);

    // Read all sentences with source-generated regex.
    Console.WriteLine("All Sentences with Source-generated Regex:");
    sw = Stopwatch.StartNew();
    match = GeneratedRegex().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.0050027
   10 Sentences with Compiled Regex:
       10 matches in 00:00:00.0181372
   10 Sentences with Source-generated Regex:
       10 matches in 00:00:00.0049145
   All Sentences with Interpreted Regex:
       13,682 matches in 00:00:00.1588303
   All Sentences with Compiled Regex:
       13,682 matches in 00:00:00.0859949
   All Sentences with Source-generated Regex:
       13,682 matches in 00:00:00.2794411
*/

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.

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 BackTrack2Example
    {
        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 BackTrack4Example
{
    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 TimeoutExample
{
    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 Group1Example
{
    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 Group2Example
{
    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.

Bezpečnost vlákna

Samotná Regex třída je bezpečná a neměnná (jen pro čtení). To znamená, že Regex objekty lze vytvořit v libovolném vlákně a sdílené mezi vlákny; odpovídající metody lze volat z libovolného vlákna a nikdy nemění žádný globální stav.

Výsledné objekty (Match a MatchCollection) vrácené Regex by však měly být použity v jednom vlákně. I když jsou mnohé z těchto objektů logicky neměnné, jejich implementace by mohly zpozdit výpočet některých výsledků, aby se zlepšil výkon, a v důsledku toho volající musí serializovat přístup k nim.

Pokud potřebujete sdílet Regex výsledné objekty ve více vláknech, lze tyto objekty převést na instance bezpečné pro přístup z více vláken voláním jejich synchronizovaných metod. S výjimkou enumerátorů jsou všechny třídy regulárních výrazů bezpečné pro vlákno nebo lze převést na objekty bezpečné pro přístup z více vláken synchronizovanou metodou.

Jediným výjimkou jsou enumerátory. Je nutné serializovat volání do výčtu kolekce. Pravidlo je, že pokud je možné vytvořit výčet kolekce na více než jednom vlákně současně, měli byste synchronizovat metody enumerátoru v kořenovém objektu kolekce procházené enumerátorem.

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.