Dela via


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 Example
{
   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, 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, är av nödvändighet en dyr.

Kommentar

En detaljerad beskrivning av prestandakonsekvenserna av att använda tolkade och kompilerade reguljära uttryck finns i Optimera prestanda för reguljära uttryck, del II: Ta hand om backtracking i BCL-teamets blogg.

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

  • Du kan skapa ett specialobjekt Regex som är nära kopplat till ett visst mönster för reguljära uttryck, kompilera det och spara det i en fristående sammansättning. Du kan anropa Regex.CompileToAssembly metoden för att kompilera och spara den.

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, tolkade reguljära uttryck och kompilerade reguljära uttryck för att förbättra programmets prestanda.

Viktigt!

Formen på metodanropet (statisk, tolkad, 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 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 RegexLib
{
   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 kontra 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.

Sammanfattningsvis rekommenderar vi att du använder tolkade reguljära uttryck när du anropar reguljära uttrycksmetoder med ett specifikt reguljärt uttryck relativt sällan. Du bör använda kompilerade reguljära uttryck när du anropar reguljära uttrycksmetoder med ett specifikt reguljärt uttryck relativt ofta. Det är svårt att fastställa det exakta tröskelvärdet med vilket de långsammare körningshastigheterna för tolkade reguljära uttryck uppväger vinsterna från deras minskade starttid, eller det tröskelvärde med vilket de långsammare starttiderna för kompilerade reguljära uttryck uppväger vinsterna från deras snabbare körningshastigheter. Det beror på olika faktorer, inklusive komplexiteten i det reguljära uttrycket och de specifika data som bearbetas. För att avgöra om tolkade eller kompilerade reguljära uttryck 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 och tolkade reguljära uttryck vid läsning av de första 10 meningarna och när du läser alla meningar i texten i Theodore Dreisers Finansiär. Som utdata från exemplet visar, när endast 10 anrop görs till reguljära uttrycksmatchningsmetoder, ger ett tolkat reguljärt uttryck bättre prestanda än ett kompilerat reguljärt 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.

using System;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      string pattern = @"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]";
      Stopwatch sw;
      Match match;
      int ctr;

      StreamReader inFile = new StreamReader(@".\Dreiser_TheFinancier.txt");
      string input = inFile.ReadToEnd();
      inFile.Close();

      // Read first ten sentences with interpreted regex.
      Console.WriteLine("10 Sentences with Interpreted Regex:");
      sw = Stopwatch.StartNew();
      Regex int10 = new Regex(pattern, RegexOptions.Singleline);
      match = int10.Match(input);
      for (ctr = 0; ctr <= 9; ctr++) {
         if (match.Success)
            // Do nothing with the match except get the next match.
            match = match.NextMatch();
         else
            break;
      }
      sw.Stop();
      Console.WriteLine("   {0} matches in {1}", ctr, sw.Elapsed);

      // Read first ten sentences with compiled regex.
      Console.WriteLine("10 Sentences with Compiled Regex:");
      sw = Stopwatch.StartNew();
      Regex comp10 = new Regex(pattern,
                   RegexOptions.Singleline | RegexOptions.Compiled);
      match = comp10.Match(input);
      for (ctr = 0; ctr <= 9; ctr++) {
         if (match.Success)
            // Do nothing with the match except get the next match.
            match = match.NextMatch();
         else
            break;
      }
      sw.Stop();
      Console.WriteLine("   {0} matches in {1}", ctr, sw.Elapsed);

      // Read all sentences with interpreted regex.
      Console.WriteLine("All Sentences with Interpreted Regex:");
      sw = Stopwatch.StartNew();
      Regex intAll = new Regex(pattern, RegexOptions.Singleline);
      match = intAll.Match(input);
      int matches = 0;
      while (match.Success) {
         matches++;
         // Do nothing with the match except get the next match.
         match = match.NextMatch();
      }
      sw.Stop();
      Console.WriteLine("   {0:N0} matches in {1}", matches, sw.Elapsed);

      // Read all sentences with compiled regex.
      Console.WriteLine("All Sentences with Compiled Regex:");
      sw = Stopwatch.StartNew();
      Regex compAll = new Regex(pattern,
                      RegexOptions.Singleline | RegexOptions.Compiled);
      match = compAll.Match(input);
      matches = 0;
      while (match.Success) {
         matches++;
         // Do nothing with the match except get the next match.
         match = match.NextMatch();
      }
      sw.Stop();
      Console.WriteLine("   {0:N0} matches in {1}", matches, sw.Elapsed);
   }
}
// The example displays the following output:
//       10 Sentences with Interpreted Regex:
//          10 matches in 00:00:00.0047491
//       10 Sentences with Compiled Regex:
//          10 matches in 00:00:00.0141872
//       All Sentences with Interpreted Regex:
//          13,443 matches in 00:00:01.1929928
//       All Sentences with Compiled Regex:
//          13,443 matches in 00:00:00.7635869
//
//       >compare1
//       10 Sentences with Interpreted Regex:
//          10 matches in 00:00:00.0046914
//       10 Sentences with Compiled Regex:
//          10 matches in 00:00:00.0143727
//       All Sentences with Interpreted Regex:
//          13,443 matches in 00:00:01.1514100
//       All Sentences with Compiled Regex:
//          13,443 matches in 00:00:00.7432921
Imports System.Diagnostics
Imports System.IO
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim pattern As String = "\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]"
        Dim sw As Stopwatch
        Dim match As Match
        Dim ctr As Integer

        Dim inFile As New StreamReader(".\Dreiser_TheFinancier.txt")
        Dim input As String = inFile.ReadToEnd()
        inFile.Close()

        ' Read first ten sentences with interpreted regex.
        Console.WriteLine("10 Sentences with Interpreted Regex:")
        sw = Stopwatch.StartNew()
        Dim int10 As New Regex(pattern, RegexOptions.SingleLine)
        match = int10.Match(input)
        For ctr = 0 To 9
            If match.Success Then
                ' Do nothing with the match except get the next match.
                match = match.NextMatch()
            Else
                Exit For
            End If
        Next
        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()
        Dim comp10 As New Regex(pattern,
                     RegexOptions.SingleLine Or RegexOptions.Compiled)
        match = comp10.Match(input)
        For ctr = 0 To 9
            If match.Success Then
                ' Do nothing with the match except get the next match.
                match = match.NextMatch()
            Else
                Exit For
            End If
        Next
        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()
        Dim intAll As New Regex(pattern, RegexOptions.SingleLine)
        match = intAll.Match(input)
        Dim matches As Integer = 0
        Do While match.Success
            matches += 1
            ' Do nothing with the match except get the next match.
            match = match.NextMatch()
        Loop
        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()
        Dim compAll As New Regex(pattern,
                       RegexOptions.SingleLine Or RegexOptions.Compiled)
        match = compAll.Match(input)
        matches = 0
        Do While match.Success
            matches += 1
            ' Do nothing with the match except get the next match.
            match = match.NextMatch()
        Loop
        sw.Stop()
        Console.WriteLine("   {0:N0} matches in {1}", matches, sw.Elapsed)
    End Sub
End Module
' The example displays output like the following:
'       10 Sentences with Interpreted Regex:
'          10 matches in 00:00:00.0047491
'       10 Sentences with Compiled Regex:
'          10 matches in 00:00:00.0141872
'       All Sentences with Interpreted Regex:
'          13,443 matches in 00:00:01.1929928
'       All Sentences with Compiled Regex:
'          13,443 matches in 00:00:00.7635869
'       
'       >compare1
'       10 Sentences with Interpreted Regex:
'          10 matches in 00:00:00.0046914
'       10 Sentences with Compiled Regex:
'          10 matches in 00:00:00.0143727
'       All Sentences with Interpreted Regex:
'          13,443 matches in 00:00:01.1514100
'       All Sentences with Compiled Regex:
'          13,443 matches in 00:00:00.7432921

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.

Reguljära uttryck: Kompilerade till en sammansättning

Med .NET kan du också skapa en sammansättning som innehåller kompilerade reguljära uttryck. Den här funktionen flyttar prestandaträffen för kompilering av reguljära uttryck från körning till designtid. Men det innebär också en del ytterligare arbete. Du måste definiera reguljära uttryck i förväg och kompilera dem till en sammansättning. Kompilatorn kan sedan referera till den här sammansättningen vid kompilering av källkod som använder sammansättningens reguljära uttryck. Varje kompilerat reguljärt uttryck i sammansättningen representeras av en klass som härleds från Regex.

Om du vill kompilera reguljära uttryck till en sammansättning anropar Regex.CompileToAssembly(RegexCompilationInfo[], AssemblyName) du metoden och skickar den en matris med RegexCompilationInfo objekt och ett AssemblyName objekt. Objekten RegexCompilationInfo representerar de reguljära uttryck som ska kompileras och objektet AssemblyName som innehåller information om sammansättningen som ska skapas.

Vi rekommenderar att du kompilerar reguljära uttryck till en sammansättning i följande situationer:

  • Om du är en komponentutvecklare som vill skapa ett bibliotek med återanvändbara reguljära uttryck.
  • Om du förväntar dig att det reguljära uttryckets mönstermatchningsmetoder ska kallas för ett obestämd antal gånger – var som helst från en eller två gånger till tusentals eller tiotusentals gånger. Till skillnad från kompilerade eller tolkade reguljära uttryck ger reguljära uttryck som kompileras till separata sammansättningar prestanda som är konsekventa oavsett antalet metodanrop.

Om du använder kompilerade reguljära uttryck för att optimera prestanda bör du inte använda reflektion för att skapa sammansättningen, läsa in motorn för reguljära uttryck och köra dess mönstermatchningsmetoder. För att undvika reflektion krävs att du inte skapar mönster för reguljära uttryck dynamiskt och att du anger eventuella mönstermatchningsalternativ, till exempel skiftlägesokänslig mönstermatchning, när sammansättningen skapas. Det kräver också att du separerar koden som skapar sammansättningen från koden som använder det reguljära uttrycket.

I följande exempel visas hur du skapar en sammansättning som innehåller ett kompilerat reguljärt uttryck. Den skapar en sammansättning med namnet RegexLib.dll med en enda reguljär uttrycksklass, SentencePattern. Den här klassen innehåller det meningsmatchande reguljära uttrycksmönstret som används i avsnittet Tolkade eller kompilerade reguljära uttryck.

using System;
using System.Reflection;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      RegexCompilationInfo SentencePattern =
                           new RegexCompilationInfo(@"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]",
                                                    RegexOptions.Multiline,
                                                    "SentencePattern",
                                                    "Utilities.RegularExpressions",
                                                    true);
      RegexCompilationInfo[] regexes = { SentencePattern };
      AssemblyName assemName = new AssemblyName("RegexLib, Version=1.0.0.1001, Culture=neutral, PublicKeyToken=null");
      Regex.CompileToAssembly(regexes, assemName);
   }
}
Imports System.Reflection
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim SentencePattern As New RegexCompilationInfo("\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]",
                                                        RegexOptions.Multiline,
                                                        "SentencePattern",
                                                        "Utilities.RegularExpressions",
                                                        True)
        Dim regexes() As RegexCompilationInfo = {SentencePattern}
        Dim assemName As New AssemblyName("RegexLib, Version=1.0.0.1001, Culture=neutral, PublicKeyToken=null")
        Regex.CompileToAssembly(regexes, assemName)
    End Sub
End Module

När exemplet kompileras till en körbar fil och körs skapas en sammansättning med namnet RegexLib.dll. En Utilities.RegularExpressions.SentencePattern klass som härleds från Regex representerar det reguljära uttrycket. I följande exempel används sedan det kompilerade reguljära uttrycket för att extrahera meningarna från texten i Theodore Dreisers Finansiären:

using System;
using System.IO;
using System.Text.RegularExpressions;
using Utilities.RegularExpressions;

public class Example
{
   public static void Main()
   {
      SentencePattern pattern = new SentencePattern();
      StreamReader inFile = new StreamReader(@".\Dreiser_TheFinancier.txt");
      string input = inFile.ReadToEnd();
      inFile.Close();

      MatchCollection matches = pattern.Matches(input);
      Console.WriteLine("Found {0:N0} sentences.", matches.Count);
   }
}
// The example displays the following output:
//      Found 13,443 sentences.
Imports System.IO
Imports System.Text.RegularExpressions
Imports Utilities.RegularExpressions

Module Example
    Public Sub Main()
        Dim pattern As New SentencePattern()
        Dim inFile As New StreamReader(".\Dreiser_TheFinancier.txt")
        Dim input As String = inFile.ReadToEnd()
        inFile.Close()

        Dim matches As MatchCollection = pattern.Matches(input)
        Console.WriteLine("Found {0:N0} sentences.", matches.Count)
    End Sub
End Module
' The example displays the following output:
'      Found 13,443 sentences.

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 Example
    {
       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 Example
{
   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:

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 Example
{
   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 Example
{
   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 Example
{
   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 parametern options för en Regex klasskonstruktor eller till parametern options 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 i subexpression. Avbildningar av icke namngivna eller implicita kapslade insamlingsgrupper inaktiveras också.

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.