Metodtips för reguljära uttryck i .NET
Motorn för reguljära uttryck i .NET är ett kraftfullt, komplett verktyg som bearbetar text baserat på mönstermatchningar i stället för att jämföra och matcha literaltext. I de flesta fall utför den mönstermatchning snabbt och effektivt. I vissa fall kan dock motorn för reguljära uttryck verka vara långsam. I extrema fall kan det till och med verka sluta svara eftersom det bearbetar en relativt liten indata under loppet av timmar eller till och med dagar.
Den här artikeln beskriver några av de metodtips som utvecklare kan använda för att säkerställa att deras reguljära uttryck uppnår optimala prestanda.
Varning
När du använder System.Text.RegularExpressions för att bearbeta ej betrodda indata skickar du en timeout. En obehörig användare kan ange indata till RegularExpressions
, vilket orsakar en Denial-of-Service-attack. ASP.NET Core Framework-API:er som använder RegularExpressions
passera en timeout.
Överväg indatakällan
I allmänhet kan reguljära uttryck acceptera två typer av indata: begränsade eller obegränsade. Begränsad inmatning är en text som kommer från en känd eller tillförlitlig källa och följer ett fördefinierat format. Obehindrat indata är en text som kommer från en opålitlig källa, till exempel en webbanvändare, och kanske inte följer ett fördefinierat eller förväntat format.
Mönster för reguljära uttryck skrivs ofta för att matcha giltiga indata. Utvecklare undersöker alltså den text som de vill matcha och skriver sedan ett reguljärt uttrycksmönster som matchar den. Utvecklare avgör sedan om det här mönstret kräver korrigering eller ytterligare utveckling genom att testa det med flera giltiga indataobjekt. När mönstret matchar alla förmodade giltiga indata deklareras det som produktionsklart och kan inkluderas i ett släppt program. Den här metoden gör ett reguljärt uttrycksmönster lämpligt för matchande begränsade indata. Det gör det dock inte lämpligt för matchning av obegränsade indata.
För att matcha obegränsade indata måste ett reguljärt uttryck hantera tre typer av text effektivt:
- Text som matchar mönster för reguljära uttryck.
- Text som inte matchar mönster för reguljära uttryck.
- Text som nästan matchar mönster för reguljära uttryck.
Den sista texttypen är särskilt problematisk för ett reguljärt uttryck som har skrivits för att hantera begränsade indata. Om det reguljära uttrycket också är beroende av omfattande bakåtspårning kan motorn för reguljära uttryck ägna en orimlig tid (i vissa fall många timmar eller dagar) åt att bearbeta till synes harmlös text.
Varning
I följande exempel används ett reguljärt uttryck som är utsatt för överdriven bakåtspårning och som sannolikt avvisar giltiga e-postadresser. Du bör inte använda den i en e-postvalideringsrutin. Om du vill ha ett reguljärt uttryck som validerar e-postadresser kan du läsa Så här: Kontrollera att strängar är i giltigt e-postformat.
Tänk dig till exempel ett vanligt men problematiskt reguljärt uttryck för att verifiera aliaset för en e-postadress. Det reguljära uttrycket ^[0-9A-Z]([-.\w]*[0-9A-Z])*$
skrivs för att bearbeta vad som anses vara en giltig e-postadress. En giltig e-postadress består av ett alfanumeriskt tecken följt av noll eller fler tecken som kan vara alfanumeriska, punkter eller bindestreck. Det reguljära uttrycket måste sluta med ett alfanumeriskt tecken. Men som följande exempel visar, även om det här reguljära uttrycket hanterar giltiga indata enkelt, är dess prestanda ineffektiv när den bearbetar nästan giltiga indata:
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
Som utdata från föregående exempel visar bearbetar motorn för reguljära uttryck det giltiga e-postaliaset i ungefär samma tidsintervall oavsett längd. Å andra sidan, när den nästan giltiga e-postadressen har fler än fem tecken, fördubblas bearbetningstiden ungefär för varje extra tecken i strängen. Därför skulle en nästan giltig sträng på 28 tecken ta över en timme att bearbeta, och en nästan giltig sträng på 33 tecken skulle ta nästan en dag att bearbeta.
Eftersom det här reguljära uttrycket utvecklades enbart genom att överväga formatet för indata som ska matchas, tar det inte hänsyn till indata som inte matchar mönstret. Den här tillsynen kan i sin tur tillåta obegränsade indata som nästan matchar mönstret för reguljära uttryck för att avsevärt försämra prestandan.
För att lösa det här problemet kan du göra följande:
När du utvecklar ett mönster bör du överväga hur bakåtspårning kan påverka prestanda för motorn för reguljära uttryck, särskilt om ditt reguljära uttryck är utformat för att bearbeta obehindrat indata. Mer information finns i avsnittet Ta hand om backtracking .
Testa ditt reguljära uttryck noggrant med ogiltiga, nästan giltiga och giltiga indata. Du kan använda Rex för att slumpmässigt generera indata för ett visst reguljärt uttryck. Rex är ett utforskningsverktyg för reguljära uttryck från Microsoft Research.
Hantera instansiering av objekt på rätt sätt
I hjärtat av . NET:s objektmodell för reguljära uttryck är System.Text.RegularExpressions.Regex klassen, som representerar motorn för reguljära uttryck. Ofta är den enskilt största faktorn som påverkar prestanda för reguljära uttryck det sätt på vilket Regex motorn används. Att definiera ett reguljärt uttryck innebär att den reguljära uttrycksmotorn kopplas nära med ett reguljärt uttrycksmönster. Den kopplingsprocessen är dyr, oavsett om det handlar om att instansiera ett Regex objekt genom att skicka konstruktorn ett reguljärt uttrycksmönster eller anropa en statisk metod genom att skicka det reguljära uttrycksmönstret och strängen som ska analyseras.
Kommentar
En detaljerad beskrivning av prestandakonsekvenserna av att använda tolkade och kompilerade reguljära uttryck finns i blogginlägget Optimera prestanda för reguljära uttryck, del II: Ta hand om bakåtspårning.
Du kan koppla motorn för reguljära uttryck med ett visst mönster för reguljära uttryck och sedan använda motorn för att matcha texten på flera sätt:
Du kan anropa en statisk mönstermatchningsmetod, till exempel Regex.Match(String, String). Den här metoden kräver inte instansiering av ett reguljärt uttrycksobjekt.
Du kan instansiera ett Regex objekt och anropa en instansmönstermatchningsmetod för ett tolkat reguljärt uttryck, vilket är standardmetoden för att binda motorn för reguljära uttryck till ett reguljärt uttrycksmönster. Det resulterar när ett Regex objekt instansieras utan ett
options
argument som innehåller Compiled flaggan.Du kan instansiera ett Regex objekt och anropa en instansmönstermatchningsmetod för ett källgenererat reguljärt uttryck. Den här tekniken rekommenderas i de flesta fall. Det gör du genom GeneratedRegexAttribute att placera attributet på en partiell metod som returnerar
Regex
.Du kan instansiera ett Regex objekt och anropa en instansmönstermatchningsmetod för ett kompilerat reguljärt uttryck. Reguljära uttrycksobjekt representerar kompilerade mönster när ett Regex objekt instansieras med ett
options
argument som innehåller Compiled flaggan.
Det specifika sätt på vilket du anropar matchningsmetoder för reguljära uttryck kan påverka programmets prestanda. I följande avsnitt beskrivs när du ska använda statiska metodanrop, källgenererade reguljära uttryck, tolkade reguljära uttryck och kompilerade reguljära uttryck för att förbättra programmets prestanda.
Viktigt!
Formen på metodanropet (statisk, tolkad, källgenererad, kompilerad) påverkar prestanda om samma reguljära uttryck används upprepade gånger i metodanrop, eller om ett program använder reguljära uttrycksobjekt i stor utsträckning.
Statiska reguljära uttryck
Metoder för statiska reguljära uttryck rekommenderas som ett alternativ till att upprepade gånger instansiera ett reguljärt uttrycksobjekt med samma reguljära uttryck. Till skillnad från reguljära uttrycksmönster som används av reguljära uttrycksobjekt cachelagras antingen åtgärdskoderna (opcodes) eller det kompilerade gemensamma mellanliggande språket (CIL) från mönster som används i statiska metodanrop internt av motorn för reguljära uttryck.
En händelsehanterare anropar till exempel ofta en annan metod för att verifiera användarindata. Det här exemplet återspeglas i följande kod, där en Button kontrolls Click händelse används för att anropa en metod med namnet IsValidCurrency
, som kontrollerar om användaren har angett en valutasymbol följt av minst en decimaltal.
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
En ineffektiv implementering av IsValidCurrency
metoden visas i följande exempel:
Kommentar
Varje metodanrop återstanterar ett Regex objekt med samma mönster. Detta innebär i sin tur att mönstret för reguljära uttryck måste kompileras om varje gång metoden anropas.
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
Du bör ersätta den föregående ineffektiva koden med ett anrop till den statiska Regex.IsMatch(String, String) metoden. Den här metoden eliminerar behovet av att instansiera ett Regex objekt varje gång du vill anropa en mönstermatchningsmetod och gör att motorn för reguljära uttryck kan hämta en kompilerad version av det reguljära uttrycket från cacheminnet.
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
Som standard cachelagras de senaste 15 senast använda statiska reguljära uttrycksmönstren. För program som kräver ett större antal cachelagrade statiska reguljära uttryck kan storleken på cachen justeras genom att ange Regex.CacheSize egenskapen.
Det reguljära uttrycket \p{Sc}+\s*\d+
som används i det här exemplet verifierar att indatasträngen har en valutasymbol och minst en decimal. Mönstret definieras enligt följande tabell:
Mönster | beskrivning |
---|---|
\p{Sc}+ |
Matchar ett eller flera tecken i kategorin Unicode-symbol, valuta. |
\s* |
Matchar noll eller fler blankstegstecken. |
\d+ |
Matchar en eller flera decimaler. |
Tolkade jämfört med källgenererade och kompilerade reguljära uttryck
Reguljära uttrycksmönster som inte är bundna till motorn för reguljära uttryck genom specifikationen Compiled av alternativet tolkas. När ett reguljärt uttrycksobjekt instansieras konverterar motorn för reguljära uttryck det reguljära uttrycket till en uppsättning åtgärdskoder. När en instansmetod anropas konverteras åtgärdskoderna till CIL och körs av JIT-kompilatorn. När en statisk reguljär uttrycksmetod anropas och det reguljära uttrycket inte kan hittas i cacheminnet konverterar motorn för reguljära uttryck till en uppsättning åtgärdskoder och lagrar dem i cacheminnet. Sedan konverteras dessa åtgärdskoder till CIL så att JIT-kompilatorn kan köra dem. Tolkade reguljära uttryck minskar starttiden på bekostnad av långsammare körningstid. På grund av den här processen används de bäst när reguljära uttryck används i ett litet antal metodanrop, eller om det exakta antalet anrop till reguljära uttrycksmetoder är okänt men förväntas vara litet. När antalet metodanrop ökar, överskrids prestandavinsten från minskad starttid av den långsammare körningshastigheten.
Reguljära uttrycksmönster som är bundna till motorn för reguljära uttryck via specifikationen Compiled för alternativet kompileras. När ett reguljärt uttrycksobjekt instansieras, eller när en statisk reguljär uttrycksmetod anropas och det reguljära uttrycket inte kan hittas i cacheminnet, konverterar motorn för reguljära uttryck till en mellanliggande uppsättning åtgärdskoder. Dessa koder konverteras sedan till CIL. När en metod anropas kör JIT-kompilatorn CIL. Till skillnad från tolkade reguljära uttryck ökar kompilerade reguljära uttryck starttiden men kör enskilda mönstermatchningsmetoder snabbare. Det innebär att prestandafördelarna med kompileringen av reguljära uttryck ökar i proportion till antalet reguljära uttrycksmetoder som anropas.
Reguljära uttrycksmönster som är bundna till motorn för reguljära uttryck genom utsmyckningen av en Regex
-returning-metod med GeneratedRegexAttribute attributet genereras av källan. Källgeneratorn, som ansluts till kompilatorn, genererar som C#-kod en anpassad Regex
-härledd implementering med logik som liknar vad som RegexOptions.Compiled
genererar i CIL. Du får alla prestandafördelar med RegexOptions.Compiled
dataflöde (mer faktiskt) och startfördelarna med Regex.CompileToAssembly
, men utan komplexiteten i CompileToAssembly
. Källan som genereras är en del av projektet, vilket innebär att den också är lätt att se och koppla bort.
För att sammanfatta rekommenderar vi att du:
- Använd tolkade reguljära uttryck när du anropar reguljära uttrycksmetoder med ett specifikt reguljärt uttryck relativt sällan.
- Använd källgenererade reguljära uttryck om du använder
Regex
i C# med argument som är kända vid kompileringstillfället och du använder ett specifikt reguljärt uttryck relativt ofta. - Använd kompilerade reguljära uttryck när du anropar reguljära uttrycksmetoder med ett specifikt reguljärt uttryck relativt ofta och du använder .NET 6 eller en tidigare version.
Det är svårt att fastställa det exakta tröskelvärdet där de långsammare körningshastigheterna för tolkade reguljära uttryck uppväger vinsterna från deras minskade starttid. Det är också svårt att fastställa det tröskelvärde där de långsammare starttiderna för källgenererade eller kompilerade reguljära uttryck uppväger vinsterna från deras snabbare körningshastigheter. Tröskelvärdena beror på olika faktorer, inklusive komplexiteten i det reguljära uttrycket och de specifika data som bearbetas. För att avgöra vilka reguljära uttryck som ger bästa prestanda för ditt specifika programscenario kan du använda Stopwatch klassen för att jämföra deras körningstider.
I följande exempel jämförs prestandan för kompilerade, källgenererade och tolkade reguljära uttryck när du läser de första 10 meningarna och när du läser alla meningar i texten i William D. Guthrie Magna Carta och Andra adresser. Som utdata från exemplet visar ger ett tolkat eller källgenererat reguljärt uttryck bättre prestanda än ett kompilerat reguljärt uttryck när endast 10 anrop görs till matchande metoder för reguljära uttryck. Ett kompilerat reguljärt uttryck ger dock bättre prestanda när ett stort antal anrop (i det här fallet över 13 000) görs.
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(" {0} matches in {1}", ctr, sw.Elapsed);
// Read first ten sentences with compiled regex.
Console.WriteLine("10 Sentences with Compiled Regex:");
sw = Stopwatch.StartNew();
Regex comp10 = new Regex(Pattern,
RegexOptions.Singleline | RegexOptions.Compiled);
match = comp10.Match(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(" {0} matches in {1}", ctr, sw.Elapsed);
// Read first ten sentences with source-generated regex.
Console.WriteLine("10 Sentences with Source-generated Regex:");
sw = Stopwatch.StartNew();
match = GeneratedRegex().Match(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(" {0} matches in {1}", ctr, sw.Elapsed);
// Read all sentences with interpreted regex.
Console.WriteLine("All Sentences with Interpreted Regex:");
sw = Stopwatch.StartNew();
Regex intAll = new(Pattern, RegexOptions.Singleline);
match = intAll.Match(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(" {0:N0} matches in {1}", matches, sw.Elapsed);
// Read all sentences with compiled regex.
Console.WriteLine("All Sentences with Compiled Regex:");
sw = Stopwatch.StartNew();
Regex compAll = new(Pattern,
RegexOptions.Singleline | RegexOptions.Compiled);
match = compAll.Match(text);
matches = 0;
while (match.Success)
{
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine(" {0:N0} matches in {1}", matches, sw.Elapsed);
// Read all sentences with source-generated regex.
Console.WriteLine("All Sentences with Source-generated Regex:");
sw = Stopwatch.StartNew();
match = GeneratedRegex().Match(text);
matches = 0;
while (match.Success)
{
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine(" {0:N0} matches in {1}", matches, sw.Elapsed);
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
*/
Det reguljära uttrycksmönstret som används i exemplet \b(\w+((\r?\n)|,?\s))*\w+[.?:;!]
, definieras enligt följande tabell:
Mönster | beskrivning |
---|---|
\b |
Starta matchningen vid en ordgräns. |
\w+ |
Matchar ett eller flera ordtecken. |
(\r?\n)|,?\s) |
Matchar antingen noll eller en vagnretur följt av ett nytt radtecken, eller noll eller ett kommatecken följt av ett blankstegstecken. |
(\w+((\r?\n)|,?\s))* |
Matchar noll eller flera förekomster av ett eller flera ordtecken som följs av antingen noll eller en vagnretur och ett nytt radtecken, eller med noll eller ett kommatecken följt av ett blankstegstecken. |
\w+ |
Matchar ett eller flera ordtecken. |
[.?:;!] |
Matchar en punkt, ett frågetecken, kolon, semikolon eller utropstecken. |
Ta hand om backtracking
Normalt använder motorn för reguljära uttryck linjär progression för att gå igenom en indatasträng och jämföra den med ett mönster för reguljära uttryck. Men när obestämda kvantifierare som *
, +
och ?
används i ett reguljärt uttrycksmönster, kan motorn för reguljära uttryck ge upp en del av lyckade partiella matchningar och återgå till ett tidigare sparat tillstånd för att söka efter en lyckad matchning för hela mönstret. Den här processen kallas backtracking.
Dricks
Mer information om bakåtspårning finns i Information om reguljärt uttrycksbeteende och bakåtspårning. Detaljerade diskussioner om bakåtspårning finns i blogginläggen Förbättringar av reguljära uttryck i .NET 7 och Optimera prestanda för reguljära uttryck.
Stöd för backtracking ger reguljära uttryck kraft och flexibilitet. Det lägger också ansvaret för att kontrollera driften av motorn för reguljära uttryck i händerna på utvecklare av reguljära uttryck. Eftersom utvecklare ofta inte är medvetna om detta ansvar spelar deras missbruk av backtracking eller beroende av överdriven backtracking ofta den viktigaste rollen för att försämra prestanda för reguljära uttryck. I värsta fall kan körningstiden fördubblas för varje ytterligare tecken i indatasträngen. Genom att använda backtracking på ett överdrivet sätt är det enkelt att skapa den programmatiska motsvarigheten till en oändlig loop om indata nästan matchar mönstret för reguljära uttryck. Motorn för reguljära uttryck kan ta timmar eller till och med dagar att bearbeta en relativt kort indatasträng.
Ofta betalar program en prestandaavgift för att använda backtracking även om backtracking inte är nödvändigt för en matchning. Det reguljära uttrycket \b\p{Lu}\w*\b
matchar till exempel alla ord som börjar med ett versalt tecken, som följande tabell visar:
Mönster | beskrivning |
---|---|
\b |
Starta matchningen vid en ordgräns. |
\p{Lu} |
Matchar ett versalt tecken. |
\w* |
Matchar noll eller fler ordtecken. |
\b |
Avsluta matchningen vid en ordgräns. |
Eftersom en ordgräns inte är samma som, eller en delmängd av, ett ordtecken, finns det ingen möjlighet att motorn för reguljära uttryck korsar en ordgräns vid matchande ordtecken. För det här reguljära uttrycket kan backtracking därför aldrig bidra till den övergripande framgången för någon matchning. Det kan bara försämra prestanda eftersom motorn för reguljära uttryck tvingas spara sitt tillstånd för varje lyckad preliminär matchning av ett ordtecken.
Om du anser att backtracking inte är nödvändigt kan du inaktivera det på ett par sätt:
Genom att ange alternativet RegexOptions.NonBacktracking (introduceras i .NET 7). Mer information finns i Icke-bakåtspårningsläge.
Genom att
(?>subexpression)
använda språkelementet, som kallas för en atomisk grupp. I följande exempel parsas en indatasträng med hjälp av två reguljära uttryck. Den första,\b\p{Lu}\w*\b
, förlitar sig på backtracking. Den andra,\b\p{Lu}(?>\w*)\b
, inaktiverar backtracking. Som utdata från exemplet visar ger de båda samma resultat: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
I många fall är backtracking viktigt för att matcha ett mönster för reguljära uttryck för att mata in text. Överdriven backtracking kan dock allvarligt försämra prestanda och ge intryck av att ett program har slutat svara. I synnerhet uppstår det här problemet när kvantifierare kapslas och texten som matchar den yttre underuttrycket är en delmängd av texten som matchar den inre underuttrycket.
Varning
Förutom att undvika överdriven backtracking bör du använda timeout-funktionen för att säkerställa att överdriven bakåtspårning inte allvarligt försämrar prestanda för reguljära uttryck. Mer information finns i avsnittet Använda timeout-värden .
Mönstret ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$
för reguljära uttryck är till exempel avsett att matcha ett delnummer som består av minst ett alfanumeriskt tecken. Alla ytterligare tecken kan bestå av ett alfanumeriskt tecken, ett bindestreck, ett understreck eller en punkt, även om det sista tecknet måste vara alfanumeriskt. Ett dollartecken avslutar delnumret. I vissa fall kan det här reguljära uttrycksmönstret uppvisa dåliga prestanda eftersom kvantifierare är kapslade och eftersom underuttrycket [0-9A-Z]
är en delmängd av underuttrycket [-.\w]*
.
I dessa fall kan du optimera prestanda för reguljära uttryck genom att ta bort de kapslade kvantifierarna och ersätta den yttre underuttrycket med en lookahead med noll bredd eller lookbehind-försäkran. Lookahead och lookbehind försäkran är fästpunkter. De flyttar inte pekaren i indatasträngen utan tittar i stället framåt eller bakom för att kontrollera om ett angivet villkor uppfylls. Delnumrets reguljära uttryck kan till exempel skrivas om som ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$
. Det här reguljära uttrycksmönstret definieras enligt följande tabell:
Mönster | beskrivning |
---|---|
^ |
Börja matchningen i början av indatasträngen. |
[0-9A-Z] |
Matcha ett alfanumeriskt tecken. Artikelnumret måste bestå av minst det här tecknet. |
[-.\w]* |
Matcha noll eller fler förekomster av ordtecken, bindestreck eller punkt. |
\$ |
Matcha ett dollartecken. |
(?<=[0-9A-Z]) |
Titta bakom det avslutande dollartecknet för att se till att föregående tecken är alfanumeriskt. |
$ |
Avsluta matchningen i slutet av indatasträngen. |
I följande exempel visas användningen av det här reguljära uttrycket för att matcha en matris som innehåller möjliga delnummer:
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.
Det reguljära uttrycksspråket i .NET innehåller följande språkelement som du kan använda för att eliminera kapslade kvantifierare. Mer information finns i Grupperingskonstruktioner.
Språkelement | beskrivning |
---|---|
(?= subexpression ) |
Positiv lookahead med noll bredd. Tittar före den aktuella positionen för att avgöra om subexpression matchar indatasträngen. |
(?! subexpression ) |
Negativ lookahead med noll bredd. Tittar före den aktuella positionen för att avgöra om subexpression den inte matchar indatasträngen. |
(?<= subexpression ) |
Noll bredd positiv lookbehind. Söker efter den aktuella positionen för att avgöra om subexpression matchar indatasträngen. |
(?<! subexpression ) |
Noll bredd negativ lookbehind. Söker efter den aktuella positionen för att avgöra om subexpression den inte matchar indatasträngen. |
Använda timeout-värden
Om dina reguljära uttryck bearbetar indata som nästan matchar mönster för reguljära uttryck kan de ofta förlita sig på överdriven bakåtspårning, vilket påverkar dess prestanda avsevärt. Förutom att noggrant överväga att använda backtracking och testa det reguljära uttrycket mot nästan matchande indata bör du alltid ange ett timeout-värde för att minimera effekten av överdriven bakåtspårning, om det inträffar.
Tidsgränsen för reguljära uttryck definierar den tidsperiod som motorn för reguljära uttryck söker efter en enda matchning innan tidsgränsen uppnås. Beroende på mönster för reguljära uttryck och indata kan körningstiden överskrida det angivna tidsgränsintervallet, men det ägnar inte mer tid åt att backa tillbaka än det angivna tidsgränsintervallet. Standardintervallet för timeout är Regex.InfiniteMatchTimeout, vilket innebär att det reguljära uttrycket inte överskrider tidsgränsen. Du kan åsidosätta det här värdet och definiera ett tidsgränsintervall enligt följande:
Regex(String, RegexOptions, TimeSpan) Anropa konstruktorn för att ange ett timeout-värde när du instansierar ett Regex objekt.
Anropa en matchningsmetod för statiska mönster, till exempel Regex.Match(String, String, RegexOptions, TimeSpan) eller Regex.Replace(String, String, String, RegexOptions, TimeSpan), som innehåller en
matchTimeout
parameter.Ange ett processomfattande värde eller ett domänomfattande appvärde med kod som
AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromMilliseconds(100));
.
Om du har definierat ett tidsgränsintervall och en matchning inte hittas i slutet av det intervallet utlöser metoden regular expression ett RegexMatchTimeoutException undantag. I undantagshanteraren kan du välja att försöka matcha igen med ett längre tidsgränsintervall, avbryta matchningsförsöket och anta att det inte finns någon matchning eller avbryta matchningsförsöket och logga undantagsinformationen för framtida analys.
I följande exempel definieras en GetWordData
metod som instansierar ett reguljärt uttryck med ett tidsgränsintervall på 350 millisekunder för att beräkna antalet ord och genomsnittligt antal tecken i ett ord i ett textdokument. Om matchningsåtgärden överskrider tidsgränsen ökas tidsgränsen med 350 millisekunder och Regex objektet återställs. Om det nya tidsgränsintervallet överskrider en sekund, så överväxlar metoden undantaget till anroparen igen.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
public class TimeoutExample
{
public static void Main()
{
RegexUtilities util = new RegexUtilities();
string title = "Doyle - The Hound of the Baskervilles.txt";
try
{
var info = util.GetWordData(title);
Console.WriteLine("Words: {0:N0}", info.Item1);
Console.WriteLine("Average Word Length: {0:N2} characters", info.Item2);
}
catch (IOException e)
{
Console.WriteLine("IOException reading file '{0}'", title);
Console.WriteLine(e.Message);
}
catch (RegexMatchTimeoutException e)
{
Console.WriteLine("The operation timed out after {0:N0} milliseconds",
e.MatchTimeout.TotalMilliseconds);
}
}
}
public class RegexUtilities
{
public Tuple<int, double> GetWordData(string filename)
{
const int MAX_TIMEOUT = 1000; // Maximum timeout interval in milliseconds.
const int INCREMENT = 350; // Milliseconds increment of timeout.
List<string> exclusions = new List<string>(new string[] { "a", "an", "the" });
int[] wordLengths = new int[29]; // Allocate an array of more than ample size.
string input = null;
StreamReader sr = null;
try
{
sr = new StreamReader(filename);
input = sr.ReadToEnd();
}
catch (FileNotFoundException e)
{
string msg = String.Format("Unable to find the file '{0}'", filename);
throw new IOException(msg, e);
}
catch (IOException e)
{
throw new IOException(e.Message, e);
}
finally
{
if (sr != null) sr.Close();
}
int timeoutInterval = INCREMENT;
bool init = false;
Regex rgx = null;
Match m = null;
int indexPos = 0;
do
{
try
{
if (!init)
{
rgx = new Regex(@"\b\w+\b", RegexOptions.None,
TimeSpan.FromMilliseconds(timeoutInterval));
m = rgx.Match(input, indexPos);
init = true;
}
else
{
m = m.NextMatch();
}
if (m.Success)
{
if (!exclusions.Contains(m.Value.ToLower()))
wordLengths[m.Value.Length]++;
indexPos += m.Length + 1;
}
}
catch (RegexMatchTimeoutException e)
{
if (e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT)
{
timeoutInterval += INCREMENT;
init = false;
}
else
{
// Rethrow the exception.
throw;
}
}
} while (m.Success);
// If regex completed successfully, calculate number of words and average length.
int nWords = 0;
long totalLength = 0;
for (int ctr = wordLengths.GetLowerBound(0); ctr <= wordLengths.GetUpperBound(0); ctr++)
{
nWords += wordLengths[ctr];
totalLength += ctr * wordLengths[ctr];
}
return new Tuple<int, double>(nWords, totalLength / nWords);
}
}
Imports System.Collections.Generic
Imports System.IO
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim util As New RegexUtilities()
Dim title As String = "Doyle - The Hound of the Baskervilles.txt"
Try
Dim info = util.GetWordData(title)
Console.WriteLine("Words: {0:N0}", info.Item1)
Console.WriteLine("Average Word Length: {0:N2} characters", info.Item2)
Catch e As IOException
Console.WriteLine("IOException reading file '{0}'", title)
Console.WriteLine(e.Message)
Catch e As RegexMatchTimeoutException
Console.WriteLine("The operation timed out after {0:N0} milliseconds",
e.MatchTimeout.TotalMilliseconds)
End Try
End Sub
End Module
Public Class RegexUtilities
Public Function GetWordData(filename As String) As Tuple(Of Integer, Double)
Const MAX_TIMEOUT As Integer = 1000 ' Maximum timeout interval in milliseconds.
Const INCREMENT As Integer = 350 ' Milliseconds increment of timeout.
Dim exclusions As New List(Of String)({"a", "an", "the"})
Dim wordLengths(30) As Integer ' Allocate an array of more than ample size.
Dim input As String = Nothing
Dim sr As StreamReader = Nothing
Try
sr = New StreamReader(filename)
input = sr.ReadToEnd()
Catch e As FileNotFoundException
Dim msg As String = String.Format("Unable to find the file '{0}'", filename)
Throw New IOException(msg, e)
Catch e As IOException
Throw New IOException(e.Message, e)
Finally
If sr IsNot Nothing Then sr.Close()
End Try
Dim timeoutInterval As Integer = INCREMENT
Dim init As Boolean = False
Dim rgx As Regex = Nothing
Dim m As Match = Nothing
Dim indexPos As Integer = 0
Do
Try
If Not init Then
rgx = New Regex("\b\w+\b", RegexOptions.None,
TimeSpan.FromMilliseconds(timeoutInterval))
m = rgx.Match(input, indexPos)
init = True
Else
m = m.NextMatch()
End If
If m.Success Then
If Not exclusions.Contains(m.Value.ToLower()) Then
wordLengths(m.Value.Length) += 1
End If
indexPos += m.Length + 1
End If
Catch e As RegexMatchTimeoutException
If e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT Then
timeoutInterval += INCREMENT
init = False
Else
' Rethrow the exception.
Throw
End If
End Try
Loop While m.Success
' If regex completed successfully, calculate number of words and average length.
Dim nWords As Integer
Dim totalLength As Long
For ctr As Integer = wordLengths.GetLowerBound(0) To wordLengths.GetUpperBound(0)
nWords += wordLengths(ctr)
totalLength += ctr * wordLengths(ctr)
Next
Return New Tuple(Of Integer, Double)(nWords, totalLength / nWords)
End Function
End Class
Samla endast in när det behövs
Reguljära uttryck i .NET stöder grupperingskonstruktioner, vilket gör att du kan gruppera ett mönster för reguljära uttryck i en eller flera underuttryck. De vanligaste grupperingskonstruktionerna i språket för reguljära .NET-uttryck är (
underuttryck)
, som definierar en numrerad insamlingsgrupp och(?<
namnunderuttryck>
)
, som definierar en namngiven insamlingsgrupp. Grupperingskonstruktioner är viktiga för att skapa backreferences och för att definiera en underuttryck som en kvantifierare tillämpas på.
Användningen av dessa språkelement har dock en kostnad. De gör att objektet GroupCollection som returneras av Match.Groups egenskapen fylls i med de senaste namnlösa eller namngivna avbildningarna. Om en enskild grupperingskonstruktion har samlat in flera delsträngar i indatasträngen fyller CaptureCollection de också i objektet som returneras av Group.Captures egenskapen för en viss insamlingsgrupp med flera Capture objekt.
Ofta används grupperingskonstruktioner endast i ett reguljärt uttryck så att kvantifierare kan tillämpas på dem. De grupper som fångas av dessa underuttryck används inte senare. Till exempel är det reguljära uttrycket \b(\w+[;,]?\s?)+[.?!]
utformat för att avbilda en hel mening. I följande tabell beskrivs språkelementen i det här reguljära uttrycksmönstret och deras effekt på Match objektets Match.Groups och Group.Captures samlingarna:
Mönster | beskrivning |
---|---|
\b |
Starta matchningen vid en ordgräns. |
\w+ |
Matchar ett eller flera ordtecken. |
[;,]? |
Matchar noll eller ett kommatecken eller semikolon. |
\s? |
Matchar noll eller ett blankstegstecken. |
(\w+[;,]?\s?)+ |
Matchar en eller flera förekomster av ett eller flera ordtecken följt av ett valfritt kommatecken eller semikolon följt av ett valfritt blankstegstecken. Det här mönstret definierar den första insamlingsgruppen, vilket är nödvändigt så att kombinationen av flera ordtecken (dvs. ett ord) följt av en valfri skiljeteckensymbol upprepas tills motorn för reguljära uttryck når slutet av en mening. |
[.?!] |
Matchar en punkt, ett frågetecken eller ett utropstecken. |
Som i följande exempel visas, när en matchning hittas, fylls både objekten GroupCollection och CaptureCollection med avbildningar från matchningen. I det här fallet finns insamlingsgruppen (\w+[;,]?\s?)
så att +
kvantifieraren kan tillämpas på den, vilket gör att mönstret för reguljära uttryck matchar varje ord i en mening. Annars skulle det matcha sista ordet i en mening.
using System;
using System.Text.RegularExpressions;
public class Group1Example
{
public static void Main()
{
string input = "This is one sentence. This is another.";
string pattern = @"\b(\w+[;,]?\s?)+[.?!]";
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index);
int grpCtr = 0;
foreach (Group grp in match.Groups)
{
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index);
int capCtr = 0;
foreach (Capture cap in grp.Captures)
{
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index);
capCtr++;
}
grpCtr++;
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// Match: 'This is one sentence.' at index 0.
// Group 0: 'This is one sentence.' at index 0.
// Capture 0: 'This is one sentence.' at 0.
// Group 1: 'sentence' at index 12.
// Capture 0: 'This ' at 0.
// Capture 1: 'is ' at 5.
// Capture 2: 'one ' at 8.
// Capture 3: 'sentence' at 12.
//
// Match: 'This is another.' at index 22.
// Group 0: 'This is another.' at index 22.
// Capture 0: 'This is another.' at 22.
// Group 1: 'another' at index 30.
// Capture 0: 'This ' at 22.
// Capture 1: 'is ' at 27.
// Capture 2: 'another' at 30.
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim input As String = "This is one sentence. This is another."
Dim pattern As String = "\b(\w+[;,]?\s?)+[.?!]"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index)
Dim grpCtr As Integer = 0
For Each grp As Group In match.Groups
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index)
Dim capCtr As Integer = 0
For Each cap As Capture In grp.Captures
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index)
capCtr += 1
Next
grpCtr += 1
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Match: 'This is one sentence.' at index 0.
' Group 0: 'This is one sentence.' at index 0.
' Capture 0: 'This is one sentence.' at 0.
' Group 1: 'sentence' at index 12.
' Capture 0: 'This ' at 0.
' Capture 1: 'is ' at 5.
' Capture 2: 'one ' at 8.
' Capture 3: 'sentence' at 12.
'
' Match: 'This is another.' at index 22.
' Group 0: 'This is another.' at index 22.
' Capture 0: 'This is another.' at 22.
' Group 1: 'another' at index 30.
' Capture 0: 'This ' at 22.
' Capture 1: 'is ' at 27.
' Capture 2: 'another' at 30.
När du endast använder underuttryck för att tillämpa kvantifierare på dem och du inte är intresserad av den insamlade texten bör du inaktivera gruppinsamlingar. Språkelementet förhindrar till exempel (?:subexpression)
den grupp som det gäller för från att samla in matchade understrängar. I följande exempel ändras mönster för reguljära uttryck från föregående exempel till \b(?:\w+[;,]?\s?)+[.?!]
. Som utdata visar förhindrar det att motorn för reguljära uttryck fyller i samlingarna GroupCollection och CaptureCollection :
using System;
using System.Text.RegularExpressions;
public class Group2Example
{
public static void Main()
{
string input = "This is one sentence. This is another.";
string pattern = @"\b(?:\w+[;,]?\s?)+[.?!]";
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index);
int grpCtr = 0;
foreach (Group grp in match.Groups)
{
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index);
int capCtr = 0;
foreach (Capture cap in grp.Captures)
{
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index);
capCtr++;
}
grpCtr++;
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// Match: 'This is one sentence.' at index 0.
// Group 0: 'This is one sentence.' at index 0.
// Capture 0: 'This is one sentence.' at 0.
//
// Match: 'This is another.' at index 22.
// Group 0: 'This is another.' at index 22.
// Capture 0: 'This is another.' at 22.
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim input As String = "This is one sentence. This is another."
Dim pattern As String = "\b(?:\w+[;,]?\s?)+[.?!]"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index)
Dim grpCtr As Integer = 0
For Each grp As Group In match.Groups
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index)
Dim capCtr As Integer = 0
For Each cap As Capture In grp.Captures
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index)
capCtr += 1
Next
grpCtr += 1
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Match: 'This is one sentence.' at index 0.
' Group 0: 'This is one sentence.' at index 0.
' Capture 0: 'This is one sentence.' at 0.
'
' Match: 'This is another.' at index 22.
' Group 0: 'This is another.' at index 22.
' Capture 0: 'This is another.' at 22.
Du kan inaktivera avbildningar på något av följande sätt:
(?:subexpression)
Använd språkelementet. Det här elementet förhindrar insamling av matchade delsträngar i den grupp som det gäller för. Den inaktiverar inte delsträngsinsamlingar i kapslade grupper.Använd alternativet ExplicitCapture . Den inaktiverar alla namnlösa eller implicita avbildningar i mönstret för reguljära uttryck. När du använder det här alternativet kan endast delsträngar som matchar namngivna grupper som definierats med
(?<name>subexpression)
språkelementet fångas in. Flaggan ExplicitCapture kan skickas till parameternoptions
för en Regex klasskonstruktor eller till parameternoptions
för en Regex statisk matchningsmetod.Använd alternativet
n
i(?imnsx)
språkelementet. Det här alternativet inaktiverar alla namnlösa eller implicita avbildningar från punkten i det reguljära uttrycksmönstret där elementet visas. Avbildningar inaktiveras antingen till slutet av mönstret eller tills(-n)
alternativet aktiverar namnlösa eller implicita avbildningar. Mer information finns i Diverse konstruktioner.Använd alternativet
n
i(?imnsx:subexpression)
språkelementet. Det här alternativet inaktiverar alla namnlösa eller implicita avbildningar isubexpression
. Avbildningar av icke namngivna eller implicita kapslade insamlingsgrupper inaktiveras också.
Trådsäkerhet
Själva Regex klassen är trådsäker och oföränderlig (skrivskyddad). Det vill säga Regex
objekt kan skapas på valfri tråd och delas mellan trådar. Matchningsmetoder kan anropas från valfri tråd och aldrig ändra något globalt tillstånd.
Resultatobjekt (Match
och MatchCollection
) som returneras av Regex
ska dock användas på en enda tråd. Även om många av dessa objekt är logiskt oföränderliga kan implementeringarna fördröja beräkningen av vissa resultat för att förbättra prestandan, och därför måste anroparna serialisera åtkomsten till dem.
Om du behöver dela Regex
resultatobjekt i flera trådar kan dessa objekt konverteras till trådsäkra instanser genom att anropa deras synkroniserade metoder. Med undantag för uppräknare är alla reguljära uttrycksklasser trådsäkra eller kan konverteras till trådsäkra objekt med en synkroniserad metod.
Uppräknare är det enda undantaget. Du måste serialisera anrop till samlingsuppräknare. Regeln är att om en samling kan räknas upp på mer än en tråd samtidigt bör du synkronisera uppräkningsmetoder på rotobjektet i samlingen som överförs av uppräknaren.
Relaterade artiklar
Title | Description |
---|---|
Information om beteende för reguljära uttryck | Undersöker implementeringen av motorn för reguljära uttryck i .NET. Artikeln fokuserar på flexibiliteten i reguljära uttryck och förklarar utvecklarens ansvar för att säkerställa effektiv och robust drift av motorn för reguljära uttryck. |
Backa | Förklarar vad backtracking är och hur det påverkar prestanda för reguljära uttryck och undersöker språkelement som ger alternativ till backtracking. |
Språk för reguljärt uttryck – snabbreferens | Beskriver elementen i det reguljära uttrycksspråket i .NET och innehåller länkar till detaljerad dokumentation för varje språkelement. |