Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Engine regulárních výrazů v .NET je výkonný plnohodnotný nástroj, který zpracovává text na základě shod vzorů, místo porovnání doslovného textu. Ve většině případů provádí porovnává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 to může dokonce vypadat, že přestalo reagovat, když zpracovává relativně malý vstup po dobu několika 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ů.
Výstraha
Při zpracování nedůvěryhodného vstupu pomocí System.Text.RegularExpressions nastavte časový limit. Zlomyslný uživatel může zadat vstup do RegularExpressions
, což způsobí útok typu odepření služby . ASP.NET Core rozhraní API, která používají RegularExpressions
pro nastavení časového limitu.
Zvažte vstupní zdroj.
Regulární výrazy mohou obecně přijímat dva typy vstupu: omezené nebo nekontrénované. Omezený vstup je text, který pochází ze známého nebo spolehlivého zdroje a řídí se předdefinovaným formátem. Neomezený 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. To znamená, že vývojáři prověří text, který chtějí spárovat, a pak napíšou vzor regulárního výrazu, který odpovídá tomuto textu. Vývojáři pak určí, jestli tento model vyžaduje opravu nebo další vylepšení tím, že ho otestují s několika platnými vstupními položkami. 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, který je vhodný pro porovnávání omezeného vstupu. 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 textu je zvláště problematický pro regulární výraz, který byl napsán pro zpracování omezeného vstupu. Pokud se tento regulární výraz také spoléhá na rozsáhlé zpětné navracení, modul regulárních výrazů může strávit nesmírně dlouhou dobu (v některých případech, mnoho hodin nebo dnů) zpracováním zdánlivě nevinného textu.
Výstraha
Následující příklad používá regulární výraz, který je náchylný k nadměrnému zpětnému procházení a pravděpodobně odmítne platné e-mailové adresy. Neměli byste ho používat v rutině ověřování e-mailu. Pokud si přejete regulární výraz, který ověřuje e-mailové adresy, podívejte se na Jak: Ověřit, že řetězce mají platný formát 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])*$
je napsán pro zpracování toho, co je považováno 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, můžete provést následující:
Při vývoji vzoru byste měli zvážit, jak by zpětné navracení mohlo ovlivnit výkon modulu regulárních výrazů, zejména pokud je regulární výraz navržený tak, aby zpracovával nekonstruovaný vstup. Další informace najdete v části Převezměte kontrolu nad vyhledáváním do minulosti.
Důkladně otestujte regulární výraz pomocí neplatného, téměř platného a platného vstupu. Pomocí Rex můžete náhodně generovat vstup pro konkrétní regulární výraz. Rex je nástroj pro zkoumání regulárních výrazů z Microsoft Research.
Správně zpracovat instanci objektu
V srdci objektového modelu regulárních výrazů .NET je třída System.Text.RegularExpressions.Regex, která představuje engine 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 se používá modul Regex. Definování regulárního výrazu zahrnuje úzkou vazbu 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 objektu Regex 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 zkompilovaný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 spojit s konkrétním vzorem regulárního výrazu a pak modul použít k porovnání textu několika různými 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 Regex a zavolat metodu přiřazování vzoru pro instance interpretovaného regulárního výrazu, což je výchozí metoda pro propojení modulu regulárních výrazů s regulárním vzorem. K tomu dochází, když je vytvořena instance objektu Regex bez argumentu
options
, který obsahuje příznak Compiled.Můžete vytvořit instanci objektu Regex a volat metodu vzorového porovnávání v rámci zdrojově generovaného regulárního výrazu. Tato technika se ve většině případů doporučuje. Uděláte to tak, že umístíte atribut GeneratedRegexAttribute na částečnou metodu, která vrátí
Regex
.Můžete vytvořit instanci objektu Regex a zavolat metodu pro vyhledávání vzorů instance zkompilovaného regulárního výrazu. Objekty regulárního výrazu představují kompilované vzory, když se vytvoří instance Regex objektu s argumentem
options
, který obsahuje příznak Compiled.
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ů se doporučují jako alternativu k opakovanému vytvoření instance objektu regulárního výrazu se stejným regulárním výrazem. Na rozdíl od vzorů regulárních výrazů, které používají objekty regulárních výrazů, jsou kódy operací (opcodes) nebo kompilovaný běžný zprostředkující jazyk (CIL) ze vzorů použitých při volání statických metod ukládány do mezipaměti regulárním výrazovým strojem.
Obslužná rutina události například často volá jinou metodu pro ověření vstupu uživatele. Tento příklad se odráží v následujícím kódu, ve kterém se událost Click ovládacího prvku Button používá k volání metody s názvem IsValidCurrency
, která kontroluje, jestli 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ří objekt Regex se stejným vzorem. To zase znamená, že vzor regulárního výrazu musí být znovu zkompilován při každém zavolání metody.
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í se ukládá do mezipaměti posledních 15 naposledy použitých vzorů statických 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 vlastnosti Regex.CacheSize.
Regulární výraz \p{Sc}+\s*\d+
, který se používá 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 prostřednictvím specifikace možnosti Compiled vázané na modul regulárních výrazů, jsou interpretovány. Při vytvoření instance objektu regulárního výrazu modul regulárních výrazů převede regulární výraz na sadu 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. Interpretované regulární výrazy zkracují dobu spuštění za cenu pomalejšího spuště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 zisk z vyššího výkonu díky kratší době spuštění převýšen pomalejší rychlostí provádění.
Vzory regulárních výrazů vázané na stroj regulárních výrazů prostřednictvím specifikace možnosti Compiled jsou kompilová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ů zkompilované regulární výrazy zvyšují dobu spouštění, ale provádějí metody porovnávání jednotlivých vzorů rychleji. V důsledku toho se zlepšení výkonu, které vyplývá z kompilace regulárního výrazu, zvyšuje úměrně k počtu volaných metod regulárního výrazu.
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 zdroj generovaný. 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 výkonu propustnosti RegexOptions.Compiled
(dokonce i více), a také spouštěcí výhody Regex.CompileToAssembly
, ale bez složitosti CompileToAssembly
. Zdroj, který je generovaný, je součástí vašeho projektu, což znamená, že je také snadno prohlédnutelný a laditelný.
Pokud chcete shrnout, doporučujeme:
- Při volání metod regulárních výrazů relativně zřídka používejte regulární výrazy interpretované pomocí a.
- Pokud používáte
Regex
v jazyce C# s argumenty známými v době kompilace a konkrétní regulární výraz používáte poměrně často, použijte regulární výrazy generované zdrojem . - Kompilované regulární výrazy používejte při častém volání metod regulárních výrazů s konkrétním výrazem, pokud 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 třídu Stopwatch k porovnání jejich časů provádění.
Následující příklad porovnává výkon kompilovaných, ze zdroje generovaný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 Williama D. Guthrieho Magna Carta a jiných proslovů. 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. Kompilovaný regulární výraz ale nabízí lepší výkon, když se provede velký počet volání (v tomto případě přes 13 000).
const string Pattern = @"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]";
static readonly HttpClient s_client = new();
[GeneratedRegex(Pattern, RegexOptions.Singleline)]
private static partial Regex GeneratedRegex();
public async static Task RunIt()
{
Stopwatch sw;
Match match;
int ctr;
string text =
await s_client.GetStringAsync("https://www.gutenberg.org/cache/epub/64197/pg64197.txt");
// 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(text);
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($" {ctr} matches in {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(text);
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($" {ctr} matches in {sw.Elapsed}");
// Read first ten sentences with source-generated regex.
Console.WriteLine("10 Sentences with Source-generated Regex:");
sw = Stopwatch.StartNew();
match = GeneratedRegex().Match(text);
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($" {ctr} matches in {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(text);
int matches = 0;
while (match.Success)
{
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine($" {matches:N0} matches in {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(text);
matches = 0;
while (match.Success)
{
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine($" {matches:N0} matches in {sw.Elapsed}");
// Read all sentences with source-generated regex.
Console.WriteLine("All Sentences with Source-generated Regex:");
sw = Stopwatch.StartNew();
match = GeneratedRegex().Match(text);
matches = 0;
while (match.Success)
{
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine($" {matches:N0} matches in {sw.Elapsed}");
return;
}
/* The example displays output similar to the following:
10 Sentences with Interpreted Regex:
10 matches in 00:00:00.0104920
10 Sentences with Compiled Regex:
10 matches in 00:00:00.0234604
10 Sentences with Source-generated Regex:
10 matches in 00:00:00.0060982
All Sentences with Interpreted Regex:
3,427 matches in 00:00:00.1745455
All Sentences with Compiled Regex:
3,427 matches in 00:00:00.0575488
All Sentences with Source-generated Regex:
3,427 matches in 00:00:00.2698670
*/
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 |
Zahajte shodu 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čce, otazníku, dvojtečce, středníku nebo vykřičníku. |
Převezměte kontrolu nad zpětným sledováním
Modul regulárních výrazů obvykle používá lineární průběh k procházení vstupního řetězce a jeho porovnání se vzorem regulárního výrazu. Pokud se ale neurčité kvantifikátory, jako jsou *
, +
a ?
používají ve vzoru regulárního výrazu, může modul regulárních výrazů vzdát část úspěšných částečných shod a vrátit se do dříve uloženého stavu, aby vyhledl úspěšnou shodu pro celý vzor. Tento proces se označuje jako navracení.
Návod
Další informace o zpětném vyhledávání najdete v tématu Podrobnosti o chování regulárních výrazů a Zpětné vyhledávání. Podrobné diskuze o zpětném sledování najdete v blogových příspěvcích Vylepšení regulárních výrazů v .NET 7 a Optimalizace výkonu regulárních výrazů.
Podpora zpětného navracení dává regulárním výrazům sílu a flexibilitu. Odpovědnost za kontrolu činnosti stroje regulárních výrazů je také vložena do rukou vývojářů regulárních výrazů. Vzhledem k tomu, že si vývojáři tuto odpovědnost často neuvědomují, jejich nesprávné používání nebo nadměrné spoléhání na zpětné sledování často hraje nejvýznamnější roli při snižování výkonnosti regulárních výrazů. V nejhorším případě se může doba provádění zdvojnásobit pro každý další znak ve vstupním řetězci. Ve skutečnosti je nadměrným používáním zpětného vyhledávání 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 trvat hodiny nebo dokonce dny, než zpracuje relativně krátký vstupní řetězec.
Aplikace často nesou výkonovou sankci za používání zpětného sledování, i když to není pro shodu nezbytné. Například regulární výraz \b\p{Lu}\w*\b
odpovídá všem slovem, která začínají velkými písmeny, jak ukazuje následující tabulka:
Vzor | Popis |
---|---|
\b |
Zahajte shodu na hranici slova. |
\p{Lu} |
Odpovídá velkým písmenu. |
\w* |
Odpovídá nule nebo více znakům tvořícím slovo. |
\b |
Ukončete shodu 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 jen zhorš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 znaků slova.
Pokud zjistíte, že zpětné navracení není nutné, můžete ho zakázat několika způsoby:
Nastavením možnosti RegexOptions.NonBacktracking (zavedená v .NET 7) Další informace naleznete v tématu režim bez zpětného sledování.
Pomocí elementu jazyka
(?>subexpression)
, který se označuje jako atomická skupina. Následující příklad analyzuje vstupní řetězec pomocí dvou regulárních výrazů. První, číslo\b\p{Lu}\w*\b
, spoléhá na zpětné sledování. Druhý,\b\p{Lu}(?>\w*)\b
, zakáž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í nezbytné pro porovnávání vzoru regulárního výrazu se vstupním textem. Nadměrné navracení však může výrazně snížit výkon a vytvořit dojem, že aplikace přestala reagovat. 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.
Výstraha
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 navržen tak, aby odpovídal číslu dílu, které se skládá z alespoň jednoho alfanumerického znaku. Všechny další znaky se můžou skládat z alfanumerického znaku, spojovníku, podtržítka nebo tečky, i když poslední znak musí být alfanumerický. Znak dolaru ukončí číslo dílu. 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 podvýraz [0-9A-Z]
je podmnožinou dílčího výrazu [-.\w]*
.
V těchto případech můžete optimalizovat výkon regulárního výrazu odebráním vnořených kvantifikátorů a nahrazením vnějšího dílčího výrazu asercí nulové šířky typu lookahead nebo asercí typu lookbehind. Kontrolní výrazy lookahead 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 pro číslo čá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 |
---|---|
^ |
Začněte shodu na začátku vstupního řetězce. |
[0-9A-Z] |
Odpovídá alfanumerickému znaku. Číslo části se musí skládat alespoň z tohoto znaku. |
[-.\w]* |
Porovná žádný nebo více výskytů libovolného znaku slova, spojovníku nebo tečky. |
\$ |
Porovná znaménko dolaru. |
(?<=[0-9A-Z]) |
Podívejte se na koncové znaménko dolaru a ujistěte se, že předchozí znak je alfanumerický. |
$ |
Ukončete 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 viz Seskupování konstrukcí.
Jazykový prvek | Popis |
---|---|
(?=
subexpression
)
|
Pozitivní pohled s nulovou šířkou. Zkontroluje dopředu od aktuální pozice, jestli subexpression odpovídá zadanému řetězci. |
(?!
subexpression
)
|
Negativní pohled s nulovou šířkou. Dívá se před aktuální pozici, aby určil, jestli subexpression neodpovídá vstupnímu řetězci. |
(?<=
subexpression
)
|
Pozitivní vzhled s nulovou šířkou. Podívá se za aktuální pozici, aby určil, jestli subexpression shoduje se s vstupním řetězcem. |
(?<!
subexpression
)
|
Negativní vzhled s nulovou šířkou. Dívá se za aktuální pozici, aby určil, jestli subexpression neodpovídá vstupnímu řetězci. |
Použijte hodnoty časového limitu
Pokud regulární výrazy zpracovávají vstup, který téměř odpovídá vzoru regulárního výrazu, může často spoléhat na nadměrné navracení, což má významný vliv na jeho 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 vypršení časového limitu je Regex.InfiniteMatchTimeout, což znamená, že regulární výraz nevyprší časový limit. Tuto hodnotu můžete přepsat a definovat interval vypršení časového limitu následujícím způsobem:
Voláním konstruktoru Regex(String, RegexOptions, TimeSpan) zadejte hodnotu časového limitu při vytvoření instance objektu Regex.
Volání metody porovnávání statických vzorů, například Regex.Match(String, String, RegexOptions, TimeSpan) nebo Regex.Replace(String, String, String, RegexOptions, TimeSpan), která obsahuje parametr
matchTimeout
.Nastavte hodnotu pro celou doménu procesu nebo aplikace pomocí kódu, jako je například
AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromMilliseconds(100));
.
Pokud jste definovali časový limit a na konci tohoto intervalu se nenajde shoda, vyvolá metoda regulárního výrazu výjimku RegexMatchTimeoutException. V obsluze výjimek můžete zkusit znovu provést shodu s delším časovým intervalem, zanechat pokusu o shodu a předpokládat, že žádná neexistuje, nebo zanechat pokusu o shodu a zaznamenat informace o výjimce pro budoucí analýzu.
Následující příklad definuje GetWordData
metodu, 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 objekt Regex se znovu spustí. Pokud nový interval časového limitu překročí jednu sekundu, metoda znovu vyvolá výjimku volajícímu.
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: {info.Item1:N0}");
Console.WriteLine($"Average Word Length: {info.Item2:N2} characters");
}
catch (IOException e)
{
Console.WriteLine($"IOException reading file '{title}'");
Console.WriteLine(e.Message);
}
catch (RegexMatchTimeoutException e)
{
Console.WriteLine($"The operation timed out after {e.MatchTimeout.TotalMilliseconds:N0} milliseconds");
}
}
}
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 seskupení v jazyce regulárních výrazů .NET jsou (
podvýraz)
, který definuje číslovanou zachytávací skupinu, a (?<
název>
podvýraz)
, který definuje pojmenovanou zachytávací skupinu. Konstrukty seskupení jsou nezbytné pro vytváření backreference a pro definování dílčího výrazu, na který se používá kvantifikátor.
Použití těchto prvků jazyka má však náklady. Způsobí, že objekt GroupCollection vrácený vlastností Match.Groups bude naplněn nejnovějšími nepojmenovanými nebo pojmenovanými zachyceními. Pokud jediný konstruktor seskupení zachytil více podřetězců ve vstupním řetězci, tyto také naplní CaptureCollection objekt vrácený Group.Captures vlastností konkrétní zachytávající skupiny s více Capture objekty.
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 podvýrazy se později nepoužívají. Například regulární výraz \b(\w+[;,]?\s?)+[.?!]
je navržený tak, aby zachytil celou větu. Následující tabulka popisuje jazykové prvky v tomto vzoru regulárního výrazu a jejich vliv na kolekce Match.Groups a Group.Captures objektu Match:
Vzor | Popis |
---|---|
\b |
Zahajte shodu 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, GroupCollection i CaptureCollection objekty jsou naplněny zachyceními z shody. V tomto případě existuje zachytávací skupina (\w+[;,]?\s?)
tak, aby na ni bylo možné použít kvantifikátor +
, což umožňuje vzor regulárního výrazu odpovídat jednotlivým slovům ve větě. Jinak by to odpovídalo 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: '{match.Value}' at index {match.Index}.");
int grpCtr = 0;
foreach (Group grp in match.Groups)
{
Console.WriteLine($" Group {grpCtr}: '{grp.Value}' at index {grp.Index}.");
int capCtr = 0;
foreach (Capture cap in grp.Captures)
{
Console.WriteLine($" Capture {capCtr}: '{cap.Value}' at {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 element jazyka (?:subexpression)
brání skupině, na kterou se vztahuje, v zachycení odpovídajících podřetězců. 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 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: '{match.Value}' at index {match.Index}.");
int grpCtr = 0;
foreach (Group grp in match.Groups)
{
Console.WriteLine($" Group {grpCtr}: '{grp.Value}' at index {grp.Index}.");
int capCtr = 0;
foreach (Capture cap in grp.Captures)
{
Console.WriteLine($" Capture {capCtr}: '{cap.Value}' at {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í můžete zakázat jedním z následujících způsobů:
Použijte prvek jazyka
(?:subexpression)
. Tento prvek zabraňuje zachycení odpovídajících podřetězí ve skupině, na kterou se vztahuje. Nevypíná zachycení podřetězců v žádné vnořené skupině.Použijte tuto ExplicitCapture možnost. Zakáže všechny nepojmenované nebo implicitní zachycení v šabloně regulárního výrazu. Při použití této možnosti lze zachytit pouze podřetězce, které odpovídají pojmenovaným skupinám definovaným pomocí jazykového prvku
(?<name>subexpression)
. Příznak ExplicitCapture lze předat parametruoptions
konstruktoru třídy Regex nebo k parametruoptions
statické metody pro porovnání Regex.V elementu jazyka
(?imnsx)
použijte možnostn
. Tato možnost zakáže všechny nepojmenované nebo implicitní zachycení od bodu ve vzoru regulárního výrazu, v němž se prvek nachází. Zachytávání se zakáže až do konce vzoru nebo dokud možnost(-n)
nepovolí nepojmenované nebo implicitní zachytávání. Další informace naleznete v tématu Různé konstrukce.V elementu jazyka
(?imnsx:subexpression)
použijte možnostn
. Tato možnost zakáže všechny nepojmenované nebo implicitní zachycení vsubexpression
. Jsou také zakázány zachycení jakýchkoli nepojmenovaných nebo implicitních vnořených skupin zachycení.
Bezpečnost vlákna
Samotná třída Regex je bezpečná pro vlákna a neměnná (pouze pro čtení). To znamená, že Regex
objekty lze vytvořit v libovolném vlákně a sdílet 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. Musíte serializovat volání na enumerátory 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.
Související články
Titulek | Popis |
---|---|
Podrobnosti o 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, co je zpětné navracení a jak ovlivňuje výkon regulárních výrazů, a zkoumá prvky jazyka, které poskytují alternativy k 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. |