Delen via


Aanbevolen procedures voor reguliere expressies in .NET

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 het patroon 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 niet-vertrouwde invoer gebruikt, geeft u een time-out door. Een kwaadwillende gebruiker kan invoer opgeven voor RegularExpressionseen Denial-of-Service-aanval. ASP.NET Core Framework-API's die gebruikmaken van RegularExpressions een time-out.

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 geldige invoer te vinden. 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 methode maakt een patroon voor reguliere expressies geschikt voor overeenkomende beperkte invoer. Het maakt het echter niet geschikt voor het matchen van ongeconstrainde invoer.

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 deze reguliere expressie ook afhankelijk is van uitgebreide backtracking, kan de engine voor reguliere expressies een coördinerende hoeveelheid tijd (in sommige gevallen veel uren of dagen) besteden aan het verwerken van schijnbaar onschuldige tekst.

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, raadpleegt u Procedure: Controleren of tekenreeksen een geldige e-mailindeling hebben.

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 toezicht kan op zijn beurt niet-getrainde invoer toestaan die bijna overeenkomt met het reguliere expressiepatroon om de prestaties aanzienlijk te verminderen.

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.

  • Test uw reguliere expressie grondig met ongeldige, bijna geldige en geldige invoer. U kunt Rex gebruiken om willekeurig invoer te genereren voor een bepaalde reguliere expressie. Rex is een reguliere expressieverkenningsprogramma van Microsoft Research.

Object instantiëring op de juiste manier verwerken

In het hart van . Het objectmodel voor reguliere expressies van NET is de System.Text.RegularExpressions.Regex klasse, die de reguliere expressie-engine 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. Dat 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

Voor een gedetailleerde bespreking van de gevolgen voor de prestaties van het gebruik van geïnterpreteerde en gecompileerde reguliere expressies, raadpleegt u het blogbericht Regular Expression Performance optimaliseren, deel II: Charge of Backtracking.

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 methode voor patroonkoppeling 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 methode voor het vergelijken van exemplaren van een geïnterpreteerde reguliere expressie aanroepen. Dit is de standaardmethode voor het binden van de engine voor reguliere expressies aan een normaal expressiepatroon. Dit resulteert wanneer een Regex object wordt geïnstantieerd zonder een options argument dat de Compiled vlag bevat.

  • U kunt een Regex object instantiëren en een patroonkoppelingsmethode voor exemplaren aanroepen van een door de bron gegenereerde reguliere expressie. Deze techniek wordt in de meeste gevallen aanbevolen. Plaats hiervoor het GeneratedRegexAttribute kenmerk op een gedeeltelijke methode die retourneert Regex.

  • U kunt een Regex object instantiëren en een methode voor het vergelijken van exemplaren van een gecompileerde reguliere expressie aanroepen. Reguliere expressieobjecten vertegenwoordigen gecompileerde patronen wanneer een Regex object wordt geïnstantieerd met een options argument dat de Compiled vlag 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 gecompileerde gecompileerde 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 gebeurtenis van Click een Button besturingselement wordt gebruikt om een methode aan te roepen met de naamIsValidCurrency, waarmee wordt gecontroleerd of de gebruiker een valutasymbool heeft ingevoerd, gevolgd door ten minste één decimaalcijfer.

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 IsValidCurrency methode weergegeven:

Notitie

Met elke methode-aanroep wordt een Regex object met hetzelfde patroon opnieuw geïnstantieert. 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 Regex.CacheSize eigenschap in te stellen.

De reguliere expressie \p{Sc}+\s*\d+ die in dit voorbeeld wordt gebruikt, controleert of de invoertekenreeks een valutasymbool en ten minste één decimaalteken heeft. 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 engine voor reguliere expressies via de specificatie van de Compiled optie, 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 zijn gebonden aan de engine voor reguliere expressies via de specificatie van de Compiled optie, 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 profiteert de prestaties van het compileren van de reguliere expressie in verhouding tot het aantal aangeroepen reguliere expressiemethoden.

Reguliere expressiepatronen die zijn gebonden aan de engine voor reguliere expressies door middel van het versieren van een Regex-retourmethode met het GeneratedRegexAttribute kenmerk, 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 in CIL wordt verzonden. U krijgt alle voordelen van RegexOptions.Compiled doorvoerprestaties van (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 reguliere expressiemethoden aanroept met een specifieke reguliere expressie relatief onregelmatig.
  • Gebruik reguliere expressies die door de bron worden gegenereerd als u Regex in C# 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 klasse gebruiken om de Stopwatch uitvoeringstijden te vergelijken.

In het volgende voorbeeld worden de prestaties van gecompileerde, brongegenereerde 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 andere adressen. Zoals de uitvoer uit het voorbeeld laat zien, biedt een geïnterpreteerde of bron gegenereerde reguliere expressie betere prestaties dan een gecompileerde reguliere expressie wanneer er slechts 10 aanroepen worden gedaan naar reguliere expressies. 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("   {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
*/

Het reguliere expressiepatroon dat in het voorbeeld wordt gebruikt, \b(\w+((\r?\n)|,?\s))*\w+[.?:;!]wordt gedefinieerd zoals wordt weergegeven in de volgende tabel:

Patroon Beschrijving
\b Begin de overeenkomst bij een woordgrens.
\w+ Komt overeen met een of meer woordtekens.
(\r?\n)|,?\s) Komt overeen met nul of één regelterugloop 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 normaal expressiepatroon, kan de engine voor reguliere expressies 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.

Tip

Zie Details van het gedrag van reguliere expressies en Backtracking voor meer informatie over backtracking. Zie de verbeteringen in de reguliere expressie in .NET 7 en blogberichten over het optimaliseren van reguliere expressieprestaties voor gedetailleerde discussies over backtracking.

Ondersteuning voor backtracking biedt 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 toepassingen een prestatiestraf voor het gebruik van backtracking, ook al is backtracking niet essentieel voor een overeenkomst. De reguliere expressie \b\p{Lu}\w*\b komt bijvoorbeeld overeen met alle woorden die beginnen met een hoofdletter, zoals in de volgende tabel wordt weergegeven:

Patroon Beschrijving
\b Begin de overeenkomst 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 algehele succes van een overeenkomst. De prestaties kunnen alleen afnemen omdat de engine voor reguliere expressies gedwongen wordt om de status op te slaan voor elke geslaagde voorlopige overeenkomst van een woordteken.

Als u vaststelt dat backtracking niet nodig is, kunt u deze op een aantal manieren uitschakelen:

  • Door de RegexOptions.NonBacktracking optie in te stellen (geïntroduceerd in .NET 7). Zie de modus Nonbacktracking voor 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*\bis afhankelijk van backtracking. De tweede, \b\p{Lu}(?>\w*)\bschakelt 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
    //       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
    

In veel gevallen is backtracking essentieel voor het aanpassen van een normaal expressiepatroon om tekst in te voeren. 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 een subset van de subexpressie [0-9A-Z] [-.\w]*is.

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 overeenkomst aan het begin van de invoertekenreeks.
[0-9A-Z] Komt overeen met een alfanumerieke teken. Het onderdeelnummer moet ten minste uit dit teken bestaan.
[-.\w]* Kom overeen met nul of meer exemplaren van een woordteken, 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 overeenkomst aan het einde van de invoertekenreeks.

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 Groeperingsconstructies voor meer informatie.

Taalelement Beschrijving
(?= subexpression ) Positieve lookahead met nulbreedte. Kijkt vooruit op de huidige positie om te bepalen of subexpression deze overeenkomt met de invoertekenreeks.
(?! subexpression ) Negatieve lookahead met nulbreedte. Kijkt vooruit op de huidige positie om te bepalen of subexpression deze niet overeenkomt met de invoertekenreeks.
(?<= subexpression ) Positieve lookbehind met nulbreedte. Kijkt achter de huidige positie om te bepalen of subexpression deze overeenkomt met de invoertekenreeks.
(?<! subexpression ) Negatieve lookbehind met nulbreedte. Kijkt achter de huidige positie om te bepalen of subexpression deze niet overeenkomt met de invoertekenreeks.

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 reguliere expressie definieert de periode die de engine voor reguliere expressies zoekt naar één overeenkomst voordat er een time-out optreedt. Afhankelijk van het reguliere expressiepatroon 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:

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 voor de aanroeper opnieuw geplaatst door de methode.

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

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 vastleggende groep wordt gedefinieerd en (?<subexpressie) een>benoemde vastleggende groep definieert. 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 Match.Groups eigenschap wordt geretourneerd, wordt gevuld met de meest recente niet-benoemde of benoemde captures. Als één groeperingsconstructie meerdere subtekenreeksen in de invoertekenreeks heeft vastgelegd, vullen ze ook het CaptureCollection object dat wordt geretourneerd door de Group.Captures eigenschap van een bepaalde groep die met meerdere Capture objecten wordt vastgelegd.

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 Match objecten Match.Groups en Group.Captures verzamelingen beschreven:

Patroon Beschrijving
\b Begin de overeenkomst 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 wordt weergegeven, worden zowel de objecten als de GroupCollection CaptureCollection objecten gevuld met opnamen van de overeenkomst. 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: '{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.

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 taalelement voorkomt bijvoorbeeld (?:subexpression) 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: '{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.

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 ExplicitCapture optie. 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 ExplicitCapture vlag kan worden doorgegeven aan de options parameter van een Regex klasseconstructor of aan de options parameter van een Regex statische overeenkomende methode.

  • Gebruik de n optie in 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. Captures worden uitgeschakeld tot het einde van het patroon of totdat de (-n) optie niet-benoemde of impliciete captures inschakelt. Zie Diverse constructies voor meer informatie.

  • Gebruik de n optie in het (?imnsx:subexpression) taalelement. Met deze optie worden alle niet-benoemde of impliciete opnamen uitgeschakeld in subexpression. Opnamen door niet-benoemde of impliciete geneste opnamegroepen worden ook uitgeschakeld.

Schroefdraadveiligheid

De Regex klasse zelf is thread veilig en onveranderbaar (alleen-lezen). Dat wil gezegd, Regex objecten kunnen worden gemaakt op elke thread en worden gedeeld tussen threads; overeenkomende methoden kunnen worden aangeroepen vanuit elke thread en nooit elke globale status wijzigen.

Resultaatobjecten (Match en MatchCollection) die worden geretourneerd door Regex , moeten echter worden gebruikt op één thread. 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 resultaatobjecten op meerdere threads wilt delen Regex , 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 naar verzamelings-inventarisaties 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.

Title Beschrijving
Details van gedrag van reguliere expressie 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.