Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
De engine voor reguliere expressies in .NET is een krachtig, volledig hulpmiddel waarmee tekst wordt verwerkt op basis van patroonovereenkomsten in plaats van letterlijke tekst te vergelijken en te vergelijken. In de meeste gevallen wordt patroonherkenning snel en efficiënt uitgevoerd. In sommige gevallen kan de engine voor reguliere expressies echter traag lijken te zijn. In extreme gevallen kan het zelfs lijken te stoppen met reageren omdat het een relatief kleine invoer verwerkt gedurende de loop van uren of zelfs dagen.
In dit artikel vindt u een overzicht van enkele aanbevolen procedures die ontwikkelaars kunnen gebruiken om ervoor te zorgen dat hun reguliere expressies optimale prestaties bereiken.
Waarschuwing
Wanneer u System.Text.RegularExpressions gebruikt om niet-vertrouwde invoer te verwerken, geeft u een time-out door. Een kwaadwillende gebruiker kan invoer opgeven voor RegularExpressionswaardoor een DoS-aanvalkan optreden. ASP.NET Core framework-API's die gebruikmaken van RegularExpressions geven een time-out door.
Houd rekening met de invoerbron
In het algemeen kunnen reguliere expressies twee typen invoer accepteren: beperkt of niet-gebonden. Beperkte invoer is een tekst die afkomstig is van een bekende of betrouwbare bron en een vooraf gedefinieerde indeling volgt. Niet-getrainde invoer is een tekst die afkomstig is van een onbetrouwbare bron, zoals een webgebruiker, en mogelijk geen vooraf gedefinieerde of verwachte indeling volgt.
Reguliere expressiepatronen worden vaak geschreven om met geldige invoer overeen te komen. Dat wil gezegd, ontwikkelaars onderzoeken de tekst die ze willen vergelijken en schrijven ze vervolgens een normaal expressiepatroon dat overeenkomt met het patroon. Ontwikkelaars bepalen vervolgens of dit patroon correctie of verdere uitwerking vereist door het te testen met meerdere geldige invoeritems. Wanneer het patroon overeenkomt met alle veronderstelde geldige invoer, wordt het aangegeven dat het gereed is voor productie en kan het worden opgenomen in een uitgebrachte toepassing. Deze aanpak maakt een patroon voor reguliere expressies geschikt voor het afstemmen op begrensde invoer. Het maakt het echter niet geschikt om onbeperkte invoer te kunnen matchen.
Als u niet-getrainde invoer wilt vergelijken, moet een reguliere expressie drie soorten tekst efficiënt verwerken:
- Tekst die overeenkomt met het reguliere expressiepatroon.
- Tekst die niet overeenkomt met het reguliere expressiepatroon.
- Tekst die bijna overeenkomt met het reguliere expressiepatroon.
Het laatste teksttype is vooral problematisch voor een reguliere expressie die is geschreven om beperkte invoer te verwerken. Als die reguliere expressie ook afhankelijk is van uitgebreide backtracking, kan de engine voor reguliere expressies veel tijd (in sommige gevallen, veel uren of dagen) aan het verwerken van schijnbaar onschuldige tekst besteden.
Waarschuwing
In het volgende voorbeeld wordt een reguliere expressie gebruikt die gevoelig is voor overmatige backtracking en die waarschijnlijk geldige e-mailadressen weigert. U moet deze niet gebruiken in een e-mailvalidatieroutine. Als u een reguliere expressie wilt die e-mailadressen valideert, bekijk dan Hoe te controleren of tekenreeksen in een geldige e-mailindeling zijn.
Denk bijvoorbeeld aan een veelgebruikte maar problematische reguliere expressie voor het valideren van de alias van een e-mailadres. De reguliere expressie ^[0-9A-Z]([-.\w]*[0-9A-Z])*$ wordt geschreven om te verwerken wat wordt beschouwd als een geldig e-mailadres. Een geldig e-mailadres bestaat uit een alfanumerieke teken, gevolgd door nul of meer tekens die alfanumeriek, punten of afbreekstreepjes kunnen zijn. De reguliere expressie moet eindigen met een alfanumerieke teken. Zoals in het volgende voorbeeld wordt weergegeven, hoewel deze reguliere expressie eenvoudig geldige invoer verwerkt, zijn de prestaties inefficiënt wanneer deze bijna geldige invoer verwerkt:
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
Zoals de uitvoer uit het vorige voorbeeld laat zien, verwerkt de engine voor reguliere expressies de geldige e-mailalias in ongeveer hetzelfde tijdsinterval, ongeacht de lengte ervan. Aan de andere kant, wanneer het bijna geldige e-mailadres meer dan vijf tekens heeft, verdubbelt de verwerkingstijd ongeveer voor elk extra teken in de tekenreeks. Daarom duurt een bijna geldige tekenreeks van 28 tekens langer dan een uur om te verwerken en een bijna geldige tekenreeks van 33 tekens duurt bijna een dag om te verwerken.
Omdat deze reguliere expressie alleen is ontwikkeld door de indeling van invoer te vergelijken, kan niet rekening worden gehouden met invoer die niet overeenkomt met het patroon. Dit verzuim kan op zijn beurt niet-beperkte invoer toestaan die bijna overeenkomt met het reguliere expressiepatroon, waardoor de prestaties aanzienlijk kunnen verslechteren.
U kunt dit probleem als volgt oplossen:
Bij het ontwikkelen van een patroon moet u overwegen hoe backtracking van invloed kan zijn op de prestaties van de reguliere expressie-engine, met name als uw reguliere expressie is ontworpen om niet-getrainde invoer te verwerken. Zie de sectie Take Charge of Backtracking voor meer informatie.
Voer een grondige test uit op uw reguliere expressie met ongeldige, bijna geldige en geldige invoer. U kunt Rex gebruiken om willekeurig invoer te genereren voor een bepaalde reguliere expressie. Rex is een hulpprogramma voor reguliere expressieverkenning van Microsoft Research.
Object instantiëring op de juiste manier verwerken
In het hart van het .NET-objectmodel voor reguliere expressies bevindt zich de System.Text.RegularExpressions.Regex-klasse, die de engine voor reguliere expressies vertegenwoordigt. Vaak is de enige grootste factor die van invloed is op de prestaties van reguliere expressies de manier waarop de Regex engine wordt gebruikt. Het definiëren van een reguliere expressie omvat het nauw koppelen van de engine voor reguliere expressies met een patroon voor reguliere expressies. Dit koppelingsproces is duur, of het nu gaat om het instantiëren van een Regex object door de constructor een normaal expressiepatroon door te geven of een statische methode aan te roepen door het normale expressiepatroon en de tekenreeks door te geven die moet worden geanalyseerd.
Notitie
Zie het blogbericht Optimizing Regular Expression Performance, Part II: Taking Charge of Backtrackingvoor een gedetailleerde bespreking van de prestatiegevolgen bij het gebruik van geïnterpreteerde en gecompileerde reguliere expressies.
U kunt de engine voor reguliere expressies koppelen aan een bepaald patroon voor reguliere expressies en vervolgens de engine gebruiken om de tekst op verschillende manieren overeen te laten komen:
U kunt een statische patroonkoppelingsmethode aanroepen, zoals Regex.Match(String, String). Voor deze methode is geen instantiëring van een reguliere expressieobject vereist.
U kunt een Regex-object instantiëren en een patroonkoppelingsmethode voor een exemplaar van een geïnterpreteerde reguliere expressie aanroepen. Dit is de standaardmethode voor het binden van de engine voor reguliere expressies aan een patroon voor reguliere expressies. Dit resulteert wanneer een Regex object wordt geïnstantieerd zonder een
optionsargument dat de vlag Compiled bevat.U kunt een Regex-object instantiëren en een exemplaarpatroonkoppelingsmethode aanroepen van een door de bron gegenereerde reguliere expressie. Deze techniek wordt in de meeste gevallen aanbevolen. Plaats hiervoor het kenmerk GeneratedRegexAttribute op een gedeeltelijke methode die
Regexretourneert.U kunt een Regex-object instantiëren en een exemplaarpatroonkoppelingsmethode van een gecompileerde reguliere expressie aanroepen. Reguliere expressieobjecten vertegenwoordigen gecompileerde patronen wanneer een Regex object wordt geïnstantieerd met een
optionsargument dat de vlag Compiled bevat.
De specifieke manier waarop u reguliere expressiekoppelingsmethoden aanroept, kan van invloed zijn op de prestaties van uw toepassing. In de volgende secties wordt besproken wanneer u statische methode-aanroepen, door bron gegenereerde reguliere expressies, geïnterpreteerde reguliere expressies en gecompileerde reguliere expressies gebruikt om de prestaties van uw toepassing te verbeteren.
Belangrijk
De vorm van de methodeaanroep (statisch, geïnterpreteerd, gegenereerd door bron, gecompileerd) beïnvloedt de prestaties als dezelfde reguliere expressie herhaaldelijk wordt gebruikt in methodeaanroepen of als een toepassing uitgebreid gebruik maakt van reguliere expressieobjecten.
Statische reguliere expressies
Statische reguliere expressiemethoden worden aanbevolen als alternatief voor het herhaaldelijk instantiëren van een reguliere expressieobject met dezelfde reguliere expressie. In tegenstelling tot reguliere expressiepatronen die worden gebruikt door reguliere expressieobjecten, worden de bewerkingscodes (opcodes) of de gecompileerde gemeenschappelijke tussentaal (CIL) van patronen die in statische methode-aanroepen worden gebruikt, intern opgeslagen in de cache van de reguliere expressie-engine.
Een gebeurtenishandler roept bijvoorbeeld vaak een andere methode aan om gebruikersinvoer te valideren. Dit voorbeeld wordt weergegeven in de volgende code, waarin de Button gebeurtenis van een Click besturingselement wordt gebruikt om een methode met de naam IsValidCurrencyaan te roepen, waarmee wordt gecontroleerd of de gebruiker een valutasymbool heeft ingevoerd, gevolgd door ten minste één decimaalteken.
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
In het volgende voorbeeld wordt een inefficiënte implementatie van de methode IsValidCurrency weergegeven:
Notitie
Elke methode roept een Regex-object met hetzelfde patroon opnieuw in. Dit betekent op zijn beurt dat het reguliere expressiepatroon telkens opnieuw moet worden gecompileerd wanneer de methode wordt aangeroepen.
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
Vervang de voorgaande inefficiënte code door een aanroep naar de statische Regex.IsMatch(String, String) methode. Met deze methode hoeft u niet telkens een Regex-object te instantiëren wanneer u een methode voor patroonkoppeling wilt aanroepen en kan de engine voor reguliere expressies een gecompileerde versie van de reguliere expressie ophalen uit de cache.
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
Standaard worden de laatste 15 laatst gebruikte statische reguliere expressiepatronen in de cache opgeslagen. Voor toepassingen waarvoor een groter aantal statische reguliere expressies in de cache is vereist, kan de grootte van de cache worden aangepast door de eigenschap Regex.CacheSize in te stellen.
De reguliere expressie \p{Sc}+\s*\d+ die in dit voorbeeld wordt gebruikt, controleert of de invoertekenreeks een valutasymbool heeft en ten minste één decimaal cijfer. Het patroon wordt gedefinieerd zoals wordt weergegeven in de volgende tabel:
| Patroon | Beschrijving |
|---|---|
\p{Sc}+ |
Komt overeen met een of meer tekens in de categorie Unicode-symbool, valuta. |
\s* |
Komt overeen met nul of meer spatietekens. |
\d+ |
Komt overeen met een of meer decimale cijfers. |
Geïnterpreteerd versus door bron gegenereerde versus gecompileerde reguliere expressies
Reguliere expressiepatronen die niet zijn gebonden aan de reguliere-uitdrukkingenengine via de specificatie van de optie Compiled worden geïnterpreteerd. Wanneer een reguliere expressieobject wordt geïnstantieerd, converteert de reguliere expressie-engine de reguliere expressie naar een set bewerkingscodes. Wanneer een exemplaarmethode wordt aangeroepen, worden de bewerkingscodes geconverteerd naar CIL en uitgevoerd door de JIT-compiler. Als een statische reguliere expressiemethode wordt aangeroepen en de reguliere expressie niet in de cache kan worden gevonden, converteert de reguliere expressie-engine de reguliere expressie naar een set bewerkingscodes en slaat deze op in de cache. Vervolgens worden deze bewerkingscodes geconverteerd naar CIL, zodat de JIT-compiler ze kan uitvoeren. Geïnterpreteerde reguliere expressies verminderen opstarttijd tegen de kosten van tragere uitvoeringstijd. Vanwege dit proces worden ze het beste gebruikt wanneer de reguliere expressie wordt gebruikt in een klein aantal methode-aanroepen of als het exacte aantal aanroepen naar reguliere expressiemethoden onbekend is, maar naar verwachting klein is. Naarmate het aantal methodeaanroepen toeneemt, wordt de prestatiewinst van een verkorte opstarttijd onderschept door de tragere uitvoeringssnelheid.
Reguliere expressiepatronen die via de specificatie van de optie Compiled aan de reguliere expressie-engine zijn gebonden, worden gecompileerd. Wanneer een reguliere expressieobject wordt geïnstantieerd of wanneer een statische reguliere expressiemethode wordt aangeroepen en de reguliere expressie niet in de cache kan worden gevonden, converteert de reguliere expressie-engine de reguliere expressie naar een tussenliggende set bewerkingscodes. Deze codes worden vervolgens geconverteerd naar CIL. Wanneer een methode wordt aangeroepen, voert de JIT-compiler het CIL uit. In tegenstelling tot geïnterpreteerde reguliere expressies, verhogen gecompileerde reguliere expressies de opstarttijd, maar voeren afzonderlijke methoden voor patroonkoppeling sneller uit. Als gevolg hiervan neemt de prestatievoordeel van het compileren van de reguliere expressie toe in verhouding tot het aantal keren dat reguliere expressiemethoden worden aangeroepen.
Reguliere expressiepatronen die zijn gebonden aan de reguliere expressie-engine door middel van het versieren van een Regex-retourmethode met het kenmerk GeneratedRegexAttribute worden bron gegenereerd. De brongenerator, die wordt aangesloten op de compiler, verzendt als C#-code een aangepaste Regex-afgeleide implementatie met logica die vergelijkbaar is met wat RegexOptions.Compiled verzendt in CIL. U krijgt alle voordelen van doorvoerprestaties van RegexOptions.Compiled (meer in feite) en de opstartvoordelen van Regex.CompileToAssembly, maar zonder de complexiteit van CompileToAssembly. De bron die wordt verzonden, maakt deel uit van uw project, wat betekent dat het ook eenvoudig kan worden weergegeven en foutopsporing kan worden uitgevoerd.
Samenvattend raden we u aan het volgende te doen:
- Gebruik geïnterpreteerde reguliere expressies wanneer u relatief onregelmatig reguliere-expressiemethoden aanroept met een specifieke reguliere expressie.
- Gebruik bron gegenereerde reguliere expressies als u
Regexin C# gebruikt met argumenten die bekend zijn tijdens het compileren en u gebruikt een specifieke reguliere expressie relatief vaak. - Gebruik gecompileerde reguliere expressies wanneer u reguliere expressiemethoden aanroept met een specifieke reguliere expressie relatief vaak en u .NET 6 of een eerdere versie gebruikt.
Het is moeilijk om de exacte drempelwaarde te bepalen waarbij de tragere uitvoeringssnelheden van geïnterpreteerde reguliere expressies opwegen tegen de toename van hun verminderde opstarttijd. Het is ook moeilijk om de drempelwaarde te bepalen waarbij de tragere opstarttijden van door de bron gegenereerde of gecompileerde reguliere expressies opwegen tegen de hogere uitvoeringssnelheden. De drempelwaarden zijn afhankelijk van verschillende factoren, waaronder de complexiteit van de reguliere expressie en de specifieke gegevens die worden verwerkt. Als u wilt bepalen welke reguliere expressies de beste prestaties bieden voor uw specifieke toepassingsscenario, kunt u de Stopwatch-klasse gebruiken om de uitvoeringstijden te vergelijken.
In het volgende voorbeeld worden de prestaties van gecompileerde, bron gegenereerde en geïnterpreteerde reguliere expressies vergeleken bij het lezen van de eerste 10 zinnen en bij het lezen van alle zinnen in de tekst van William D. Guthrie's Magna Carta en Other Addresses. Zoals de uitvoer uit het voorbeeld laat zien, biedt een geïnterpreteerde of bron-gegeneerde reguliere expressie betere prestaties dan een gecompileerde reguliere expressie wanneer er slechts 10 aanroepen worden gedaan naar reguliere expressie-matchingmethoden. Een gecompileerde reguliere expressie biedt echter betere prestaties wanneer een groot aantal aanroepen (in dit geval meer dan 13.000) worden uitgevoerd.
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
*/
Het reguliere expressiepatroon dat wordt gebruikt in het voorbeeld, \b(\w+((\r?\n)|,?\s))*\w+[.?:;!], wordt gedefinieerd zoals wordt weergegeven in de volgende tabel:
| Patroon | Beschrijving |
|---|---|
\b |
Start de match bij een woordgrens. |
\w+ |
Komt overeen met een of meer woordtekens. |
(\r?\n)|,?\s) |
Komt overeen met nul of één regelterugkeer gevolgd door een nieuw regelteken, of nul of één komma gevolgd door een spatieteken. |
(\w+((\r?\n)|,?\s))* |
Komt overeen met nul of meer exemplaren van een of meer woordtekens die worden gevolgd door nul of één regelterugloop en een nieuw regelteken, of door nul of één komma gevolgd door een spatieteken. |
\w+ |
Komt overeen met een of meer woordtekens. |
[.?:;!] |
Komt overeen met een punt, vraagteken, dubbele punt, puntkomma of uitroepteken. |
Neem de leiding over Backtracking
Normaal gesproken gebruikt de engine voor reguliere expressies lineaire voortgang om door een invoerreeks te lopen en deze te vergelijken met een normaal expressiepatroon. Wanneer echter onbepaalde kwantificatoren zoals *, +en ? worden gebruikt in een patroon voor reguliere expressies, kan de reguliere expressie-engine een deel van geslaagde gedeeltelijke overeenkomsten opgeven en terugkeren naar een eerder opgeslagen status om te zoeken naar een geslaagde overeenkomst voor het hele patroon. Dit proces wordt backtracking genoemd.
Hint
Voor meer informatie over backtracking, zie Details over het gedrag van reguliere expressies en Backtracking. Zie voor gedetailleerde discussies over backtracking de blogposts Verbeteringen voor reguliere expressies in .NET 7 en Optimalisatie van reguliere expressieprestaties.
Backtracking-ondersteuning geeft reguliere expressies kracht en flexibiliteit. Het plaatst ook de verantwoordelijkheid voor het beheren van de werking van de engine voor reguliere expressies in handen van ontwikkelaars van reguliere expressies. Omdat ontwikkelaars zich vaak niet bewust zijn van deze verantwoordelijkheid, speelt hun misbruik van backtracking of afhankelijkheid van overmatige backtracking vaak de belangrijkste rol bij het verminderen van de prestaties van reguliere expressies. In een slechtst scenario kan de uitvoeringstijd verdubbelen voor elk extra teken in de invoertekenreeks. Door backtracking te gebruiken, is het zelfs eenvoudig om het programmatische equivalent van een eindeloze lus te maken als invoer bijna overeenkomt met het reguliere expressiepatroon. Het kan uren of zelfs dagen duren voordat de engine voor reguliere expressies een relatief korte invoertekenreeks verwerkt.
Vaak betalen applicaties een prestatieboete voor het gebruik van backtracking, ook al is backtracking niet essentieel voor een overeenstemming. De reguliere expressie \b\p{Lu}\w*\b bijvoorbeeld overeenkomt met alle woorden die beginnen met een hoofdletter, zoals in de volgende tabel wordt weergegeven:
| Patroon | Beschrijving |
|---|---|
\b |
Start de match bij een woordgrens. |
\p{Lu} |
Komt overeen met een hoofdletter. |
\w* |
Komt overeen met nul of meer woordtekens. |
\b |
Beëindig de overeenkomst op een woordgrens. |
Omdat een woordgrens niet hetzelfde is als of een subset van een woordteken, is het niet mogelijk dat de reguliere expressie-engine een woordgrens overschrijdt bij het koppelen van woordtekens. Daarom kan backtracking voor deze reguliere expressie nooit bijdragen aan het succes van een match. De prestaties kunnen alleen afnemen omdat de reguliere-expressiemachine gedwongen wordt om zijn status op te slaan bij elke succesvolle voorlopige overeenkomst van een woordteken.
Als u vaststelt dat backtracking niet nodig is, kunt u deze op een aantal manieren uitschakelen:
Door de optie RegexOptions.NonBacktracking in te stellen (geïntroduceerd in .NET 7). Zie Niet-backtrackingmodusvoor meer informatie.
Met behulp van het
(?>subexpression)taalelement, ook wel een atomische groep genoemd. In het volgende voorbeeld wordt een invoertekenreeks geparseerd met behulp van twee reguliere expressies. De eerste,\b\p{Lu}\w*\b, is afhankelijk van terugzoeken. De tweede,\b\p{Lu}(?>\w*)\b, schakelt backtracking uit. Zoals de uitvoer uit het voorbeeld laat zien, produceren ze beide hetzelfde resultaat: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 // CapitalImports 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
In veel gevallen is backtracking essentieel voor het matchen van een reguliere-expressiepatroon met inputtekst. Overmatige backtracking kan echter de prestaties ernstig verminderen en de indruk creëren dat een toepassing niet meer reageert. Dit probleem ontstaat met name wanneer kwantificatoren zijn genest en de tekst die overeenkomt met de buitenste subexpressie een subset is van de tekst die overeenkomt met de binnenste subexpressie.
Waarschuwing
Naast het voorkomen van overmatige backtracking, moet u de time-outfunctie gebruiken om ervoor te zorgen dat overmatige backtracking de prestaties van reguliere expressies niet ernstig verslechtert. Zie de sectie Time-outwaarden gebruiken voor meer informatie.
Het reguliere expressiepatroon ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$ is bijvoorbeeld bedoeld om een onderdeelnummer te vinden dat uit ten minste één alfanumerieke teken bestaat. Eventuele extra tekens kunnen bestaan uit een alfanumerieke teken, een afbreekstreepje, een onderstrepingsteken of een punt, hoewel het laatste teken alfanumeriek moet zijn. Een dollarteken beëindigt het onderdeelnummer. In sommige gevallen kan dit reguliere expressiepatroon slechte prestaties vertonen omdat kwantificatoren zijn genest en omdat de subexpressie [0-9A-Z] een subset is van de subexpressie [-.\w]*.
In deze gevallen kunt u de prestaties van reguliere expressies optimaliseren door de geneste kwantificatoren te verwijderen en de buitenste subexpressie te vervangen door een lookahead met nul breedte of lookbehind-assertie. Lookahead- en lookbehind-asserties zijn ankers. Ze verplaatsen de aanwijzer niet in de invoertekenreeks, maar kijk vooruit of achter om te controleren of aan een opgegeven voorwaarde is voldaan. De reguliere expressie van het onderdeelnummer kan bijvoorbeeld worden herschreven als ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$. Dit reguliere expressiepatroon wordt gedefinieerd zoals wordt weergegeven in de volgende tabel:
| Patroon | Beschrijving |
|---|---|
^ |
Begin de match aan het begin van de invoerreeks. |
[0-9A-Z] |
Komt overeen met een alfanumerieke teken. Het onderdeelnummer moet ten minste uit dit teken bestaan. |
[-.\w]* |
Controleer of er nul of meer voorkomens zijn van een woordenkarakter, afbreekstreepje of punt. |
\$ |
Komt overeen met een dollarteken. |
(?<=[0-9A-Z]) |
Kijk achter het laatste dollarteken om ervoor te zorgen dat het vorige teken alfanumeriek is. |
$ |
Beëindig de match aan het einde van de invoerstring. |
In het volgende voorbeeld ziet u hoe deze reguliere expressie overeenkomt met een matrix met mogelijke onderdeelnummers:
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.
De reguliere expressietaal in .NET bevat de volgende taalelementen die u kunt gebruiken om geneste kwantificatoren te elimineren. Zie Groeperingsconstructiesvoor meer informatie.
| Taalelement | Beschrijving |
|---|---|
(?=
subexpression
)
|
Positieve lookahead met nulbreedte. Kijkt vooruit op de huidige positie om te bepalen of subexpression overeenkomt met de invoertekenreeks. |
(?!
subexpression
)
|
Negatieve lookahead met nulbreedte. Kijkt vooruit op de huidige positie om te bepalen of subexpression niet overeenkomt met de invoertekenreeks. |
(?<=
subexpression
)
|
Positieve achterwaartse verwijzing met nulbreedte. Kijkt achter de huidige positie om te bepalen of subexpression overeenkomt met de invoertekenreeks. |
(?<!
subexpression
)
|
Negatieve lookbehind met nulbreedte. Er wordt achter de huidige positie gekeken om te bepalen of subexpression niet overeenkomt met de invoerstring. |
Time-outwaarden gebruiken
Als uw reguliere expressies invoer verwerkt die bijna overeenkomt met het reguliere expressiepatroon, kan dit vaak afhankelijk zijn van overmatige backtracking, wat de prestaties aanzienlijk beïnvloedt. Naast het zorgvuldig overwegen van het gebruik van backtracking en het testen van de reguliere expressie op bijna overeenkomende invoer, moet u altijd een time-outwaarde instellen om het effect van overmatige backtracking te minimaliseren, als dit gebeurt.
Het time-outinterval van de regelmatige expressie definieert de tijdsperiode waarin de regelmatige expressie-engine zoekt naar een enkele overeenkomst voordat er een time-out optreedt. Afhankelijk van het patroon van de regelmatige expressie en de invoertekst kan de uitvoeringstijd langer zijn dan het opgegeven time-outinterval, maar er wordt niet meer tijd besteed aan backtracking dan het opgegeven time-outinterval. Het standaardtime-outinterval is Regex.InfiniteMatchTimeout, wat betekent dat er geen time-out optreedt voor de reguliere expressie. U kunt deze waarde als volgt overschrijven en een time-outinterval definiëren:
Roep de Regex(String, RegexOptions, TimeSpan) constructor aan om een time-outwaarde op te geven wanneer u een Regex-object instantieert.
Roep een statische patroonherkenningsmethode aan, zoals Regex.Match(String, String, RegexOptions, TimeSpan) of Regex.Replace(String, String, String, RegexOptions, TimeSpan), die een
matchTimeout-parameter bevat.Stel een procesbrede waarde of een domeinbrede app-waarde in met code zoals
AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromMilliseconds(100));.
Als u een time-outinterval hebt gedefinieerd en er geen overeenkomst wordt gevonden aan het einde van dat interval, genereert de reguliere expressiemethode een RegexMatchTimeoutException uitzondering. In de uitzonderingshandler kunt u ervoor kiezen om de overeenkomst opnieuw uit te voeren met een langer time-outinterval, de overeenkomstpoging af te laten en ervan uit te gaan dat er geen overeenkomst is, of de overeenkomstpoging af te schaffen en de uitzonderingsgegevens voor toekomstige analyse te registreren.
In het volgende voorbeeld wordt een GetWordData methode gedefinieerd waarmee een reguliere expressie wordt geïnstitueerd met een time-outinterval van 350 milliseconden om het aantal woorden en het gemiddelde aantal tekens in een woord in een tekstdocument te berekenen. Als er een time-out optreedt voor de overeenkomende bewerking, wordt het time-outinterval met 350 milliseconden verhoogd en wordt het Regex object opnieuw geïnantieerd. Als het nieuwe time-outinterval langer is dan één seconde, wordt de uitzondering door de methode opnieuw naar de aanroeper gegooid.
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
Alleen vastleggen wanneer dat nodig is
Reguliere expressies in .NET-ondersteuningsgroeperingsconstructies, waarmee u een normaal expressiepatroon kunt groeperen in een of meer subexpressies. De meestgebruikte groeperingsconstructies in de reguliere .NET-expressietaal zijn (subexpressie), waarmee een genummerde groep wordt gedefinieerd en (?<naam>subexpressie), waarmee een benoemde vastleggende groep wordt gedefinieerd. Groeperingsconstructies zijn essentieel voor het maken van backreferences en voor het definiëren van een subexpressie waarop een kwantifier wordt toegepast.
Het gebruik van deze taalelementen heeft echter kosten. Ze zorgen ervoor dat het GroupCollection-object dat door de eigenschap Match.Groups wordt geretourneerd, wordt gevuld met de meest recente niet-benoemde of benoemde captures. Als een enkele groepeerconstructie meerdere subtekenreeksen in de invoertekenreeks heeft vastgelegd, vullen ze ook het CaptureCollection-object dat wordt geretourneerd door de eigenschap Group.Captures van een bepaalde vastleggende groep met verscheidene Capture-objecten.
Groepeerconstructies worden vaak alleen in een reguliere expressie gebruikt, zodat kwantificatoren erop kunnen worden toegepast. De groepen die door deze subexpressies worden vastgelegd, worden later niet gebruikt. De reguliere expressie \b(\w+[;,]?\s?)+[.?!] is bijvoorbeeld ontworpen om een hele zin vast te leggen. In de volgende tabel worden de taalelementen in dit reguliere expressiepatroon en het effect ervan op de MatchMatch.Groups en Group.Captures verzamelingen van het object beschreven:
| Patroon | Beschrijving |
|---|---|
\b |
Start de match bij een woordgrens. |
\w+ |
Komt overeen met een of meer woordtekens. |
[;,]? |
Komt overeen met nul of één komma of puntkomma. |
\s? |
Komt overeen met nul of één spatieteken. |
(\w+[;,]?\s?)+ |
Komt overeen met een of meer exemplaren van een of meer woordtekens, gevolgd door een optionele komma of puntkomma, gevolgd door een optioneel spatieteken. Dit patroon definieert de eerste vastleggende groep, die nodig is, zodat de combinatie van meerdere woordtekens (dat wil zeggen een woord) gevolgd door een optioneel interpunctiesymbool wordt herhaald totdat de reguliere expressie-engine het einde van een zin bereikt. |
[.?!] |
Komt overeen met een punt, vraagteken of uitroepteken. |
Zoals in het volgende voorbeeld staat, worden zowel de GroupCollection als CaptureCollection objecten gevuld met vastleggingen van de match. In dit geval bestaat de vastleggende groep (\w+[;,]?\s?), zodat de + kwantificator erop kan worden toegepast, waardoor het reguliere expressiepatroon overeenkomt met elk woord in een zin. Anders komt het overeen met het laatste woord in een zin.
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.
Wanneer u subexpressies alleen gebruikt om kwantificatoren toe te passen en u niet geïnteresseerd bent in de vastgelegde tekst, moet u groepsopnamen uitschakelen. Het (?:subexpression) taalelement voorkomt bijvoorbeeld dat de groep waarop het van toepassing is, overeenkomende subtekenreeksen vastlegt. In het volgende voorbeeld wordt het reguliere expressiepatroon uit het vorige voorbeeld gewijzigd in \b(?:\w+[;,]?\s?)+[.?!]. Zoals in de uitvoer wordt weergegeven, voorkomt u dat de engine voor reguliere expressies de GroupCollection en CaptureCollection verzamelingen invult:
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.
U kunt opnamen op een van de volgende manieren uitschakelen:
Gebruik het
(?:subexpression)taalelement. Dit element voorkomt het vastleggen van overeenkomende subtekenreeksen in de groep waarop het van toepassing is. Er worden geen subtekenreeksopnamen in geneste groepen uitgeschakeld.Gebruik de optie ExplicitCapture. Hiermee worden alle niet-benoemde of impliciete captures in het reguliere expressiepatroon uitgeschakeld. Wanneer u deze optie gebruikt, kunnen alleen subtekenreeksen worden vastgelegd die overeenkomen met benoemde groepen die zijn gedefinieerd met het
(?<name>subexpression)taalelement. De vlag ExplicitCapture kan worden doorgegeven aan de parameteroptionsvan een Regex klasseconstructor of aan de parameteroptionsvan een Regex statische vergelijkingsmethode.Gebruik de optie
nin het(?imnsx)taalelement. Met deze optie worden alle niet-benoemde of impliciete opnamen uitgeschakeld vanaf het punt in het reguliere expressiepatroon waarop het element wordt weergegeven. Capturen worden uitgeschakeld tot het einde van het patroon of totdat de optie(-n)niet-benoemde of impliciete capturen inschakelt. Zie Diverse constructiesvoor meer informatie.Gebruik de optie
nin het(?imnsx:subexpression)taalelement. Met deze optie worden alle niet-benoemde of impliciete opnamen insubexpressionuitgeschakeld. Opnamen door niet-benoemde of impliciete geneste opnamegroepen worden ook uitgeschakeld.
Schroefdraadveiligheid
De Regex-klasse zelf is thread-safe en onveranderbaar (alleen-lezen). Dat wil gezegd, Regex objecten kunnen worden gemaakt op elke thread en gedeeld tussen threads; overeenkomende methoden kunnen worden aangeroepen vanuit elke thread en kunnen nooit een globale status wijzigen.
Resultaatobjecten (Match en MatchCollection) die door Regex worden geretourneerd, moeten echter op één thread worden gebruikt. Hoewel veel van deze objecten logisch onveranderbaar zijn, kunnen hun implementaties de berekening van sommige resultaten vertragen om de prestaties te verbeteren. Hierdoor moeten bellers de toegang tot deze objecten serialiseren.
Als u Regex resultaatobjecten op meerdere threads wilt delen, kunnen deze objecten worden geconverteerd naar threadveilige exemplaren door hun gesynchroniseerde methoden aan te roepen. Met uitzondering van enumerators zijn alle reguliere expressieklassen thread-veilig of kunnen ze worden geconverteerd naar threadveilige objecten met een gesynchroniseerde methode.
Enumerators zijn de enige uitzondering. U moet aanroepen van verzameling-enumeratoren serialiseren. De regel is dat als een verzameling tegelijkertijd op meerdere threads kan worden geïnventariseerd, u de enumeratormethoden moet synchroniseren op het hoofdobject van de verzameling die wordt doorkruist door de enumerator.
Verwante artikelen
| Titel | Beschrijving |
|---|---|
| details van het gedrag van reguliere expressies | Bekijkt de implementatie van de engine voor reguliere expressies in .NET. Het artikel richt zich op de flexibiliteit van reguliere expressies en legt de verantwoordelijkheid van de ontwikkelaar uit voor de efficiënte en robuuste werking van de engine voor reguliere expressies. |
| Backtracking | Legt uit wat backtracking is en hoe dit van invloed is op de prestaties van reguliere expressies en bekijkt taalelementen die alternatieven bieden voor backtracking. |
| Reguliere expressietaal - Snelzoekgids | Beschrijft de elementen van de reguliere expressietaal in .NET en bevat koppelingen naar gedetailleerde documentatie voor elk taalelement. |