Empfohlene Vorgehensweisen für die Verwendung von regulären Ausdrücken in .NET
Die Engine für reguläre Ausdrücke in .NET ist ein leistungsstarkes Tool mit vollem Funktionsumfang, das Texte auf Grundlage von Musterübereinstimmungen verarbeitet, anstatt Literaltext zu vergleichen und nach Übereinstimmungen mit diesem zu suchen. In den meisten Fällen wird die Suche nach Musterabgleichen schnell und effizient ausgeführt. Gelegentlich kann die Engine für reguläre Ausdrücke jedoch langsam wirken. In Extremfällen kann auch der Eindruck entstehen, dass das Modul nicht mehr reagiert, wenn für die Verarbeitung relativ kleiner Eingaben mehrere Stunden oder sogar Tage benötigt werden.
In diesem Artikel werden einige bewährte Methoden erläutert, mit denen Entwickler*innen sicherstellen können, dass ihre regulären Ausdrücke die optimale Leistung erzielen.
Warnung
Übergeben Sie ein Timeout, wenn Sie System.Text.RegularExpressions zum Verarbeiten nicht vertrauenswürdiger Eingaben verwenden. Böswillige Benutzer können Eingaben für RegularExpressions
bereitstellen, um einen Denial-of-Service-Angriff durchzuführen. ASP.NET Core-Framework-APIs, die RegularExpressions
verwenden, übergeben ein Timeout.
Bedenken der Eingabequelle
Im Allgemeinen können reguläre Ausdrücke zwei Arten von Eingaben annehmen: eingeschränkte und nicht eingeschränkte. Bei eingeschränkten Eingaben handelt es sich um einen Text, der aus einer bekannten oder zuverlässigen Quelle stammt und einem vordefinierten Format entspricht. Eine nicht eingeschränkte Eingabe ist ein Text, der aus einer unzuverlässigen Quelle stammt (z. B. von Webbenutzer*innen) und keinem vordefinierten oder erwarteten Format entspricht.
Muster regulärer Ausdrücke werden oft für den Abgleich mit gültigen Eingaben geschrieben. Das bedeutet, dass ein Entwickler den Text überprüft, mit dem eine Übereinstimmung erzielt werden soll, und anschließend ein entsprechendes Muster für reguläre Ausdrücke erstellt. Dann ermittelt der Entwickler, ob dieses Muster Korrekturen oder Ausarbeitungen erfordert, indem er es mit mehreren gültigen Eingabeelementen testet. Wenn das Muster mit allen als gültig geltenden Eingaben übereinstimmt, wird es als produktionsbereit deklariert und kann in eine veröffentlichte Anwendung eingeschlossen werden. Durch diesen Ansatz eignet sich das Muster des regulären Ausdrucks für den Abgleich mit eingeschränkten Eingaben. Es eignet sich jedoch nicht für den Abgleich mit nicht eingeschränkten Eingaben.
Zum Abgleichen nicht eingeschränkter Eingaben muss ein regulärer Ausdruck drei Arten von Text effizient verarbeiten:
- Text, der mit dem Muster eines regulären Ausdrucks übereinstimmt.
- Text, der nicht mit dem Muster des regulären Ausdrucks übereinstimmt
- Text, der fast mit dem Muster eines regulären Ausdrucks übereinstimmt.
Der letzte Texttyp ist besonders problematisch für reguläre Ausdrücke, die für die Behandlung eingeschränkter Eingaben vorgesehen sind. Wenn ein solcher regulärer Ausdruck zudem auf umfangreicher Rückverfolgung beruht, kann die Engine für reguläre Ausdrücke für die Verarbeitung von scheinbar harmlosem Text übermäßig lange Zeit brauchen (in manchen Fällen mehrere Stunden oder Tage).
Warnung
Im folgenden Beispiel wird ein regulärer Ausdruck verwendet, der für übermäßige Rückverfolgung anfällig ist und wahrscheinlich gültige E-Mail-Adressen zurückweist. Sie sollten ihn nicht in einer E-Mail-Validierungsroutine verwenden. Einen regulären Ausdruck, der E-Mail-Adressen überprüft, finden Sie unter Vorgehensweise: Überprüfen, ob Zeichenfolgen ein gültiges E-Mail-Format aufweisen.
Als Beispiel dient hier ein häufig verwendeter, jedoch problematischer regulärer Ausdruck zum Überprüfen des Alias einer E-Mail-Adresse. Der reguläre Ausdruck ^[0-9A-Z]([-.\w]*[0-9A-Z])*$
dient zum Verarbeiten einer als gültig geltenden E-Mail-Adresse. Eine gültige E-Mail-Adresse besteht aus einem alphanumerischen Zeichen gefolgt von keinem oder mehreren Zeichen, bei denen es sich um alphanumerische Zeichen, Punkte oder Bindestriche handeln kann. Der reguläre Ausdruck muss mit einem alphanumerischen Zeichen enden. Wie das folgende Beispiel zeigt, kann dieser reguläre Ausdruck gültige Eingaben problemlos verarbeiten, bei der Verarbeitung von fast gültigen Eingaben ist seine Leistung jedoch schlecht:
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
Die Ausgabe im obigen Beispiel zeigt, dass die Engine für reguläre Ausdrücke den gültigen E-Mail-Alias in etwa demselben Zeitintervall verarbeitet, unabhängig von dessen Länge. Wenn die fast gültige E-Mail-Adresse mehr als fünf Zeichen aufweist, verdoppelt sich die Verarbeitungszeit allerdings nahezu für jedes weitere Zeichen in der Zeichenfolge. Die Verarbeitung einer fast gültigen Zeichenfolge mit 28 Zeichen würde daher mehr als eine Stunde und die einer fast gültigen Zeichenfolge mit 33 Zeichen ungefähr einen Tag dauern.
Da dieser reguläre Ausdruck ausschließlich im Hinblick auf das Format der abzugleichenden Eingaben entwickelt wurde, werden Eingaben, die nicht mit dem Muster übereinstimmen, nicht berücksichtigt. Diese „Versehen“ können wiederum dazu führen, dass die Leistung durch nicht eingeschränkte Eingaben, die fast mit dem Muster des regulären Ausdrucks übereinstimmen, erheblich beeinträchtigt wird.
Um dieses Problem zu beheben, können Sie wie folgt vorgehen:
Beim Erstellen eines Musters sollten Sie berücksichtigen, wie sich das Zurückverfolgen auf die Leistung der Engine für reguläre Ausdrücke auswirken könnte. Dies gilt insbesondere, wenn ein regulärer Ausdruck für die Verarbeitung nicht eingeschränkter Eingaben vorgesehen ist. Weitere Informationen finden Sie im Abschnitt Steuern der Rückverfolgung.
Testen Sie den regulären Ausdruck gründlich mit ungültigen, fast gültigen und gültigen Eingaben. Sie können Rex verwenden, um nach dem Zufallsprinzip Eingaben für einen bestimmten regulären Ausdruck zu generieren. Rex ist ein Tool zum Untersuchen von regulären Ausdrücken von Microsoft Research.
Angemessene Behandlung der Objektinstanziierung
Den Kern des .NET-Objektmodells für reguläre Ausdrücke bildet die System.Text.RegularExpressions.Regex-Klasse, die die Engine für reguläre Ausdrücke darstellt. Häufig ist die einzige Hauptursache für Leistungsbeeinträchtigungen bei regulären Ausdrücken die Art, wie die Regex-Engine verwendet wird. Das Definieren eines regulären Ausdrucks beinhaltet das enge Verbinden der Engine für reguläre Ausdrücke mit einem Muster für reguläre Ausdrücke. Dieser Kopplungsprozess ist teuer, unabhängig davon, ob ein Regex-Objekt instanziiert wird, indem er seinen Konstruktor über ein Muster mit regulären Ausdrücken oder durch Aufrufen einer statischen Methode das Muster des regulären Ausdrucks und die zu analysierende Zeichenfolge übergibt.
Hinweis
Eine ausführliche Erläuterung der Leistungsauswirkungen der Verwendung interpretierter und kompilierter regulärer Ausdrücke finden Sie im Blogbeitrag Optimieren der Leistung regulärer Ausdrücke, Teil II: Steuern der Rückverfolgung.
Sie können die Engine für reguläre Ausdrücke mit einem bestimmten Muster für reguläre Ausdrücke verknüpfen und die Engine dann verwenden, um den Text auf verschiedene Weise abzugleichen:
Sie können eine statische Methode für Musterübereinstimmungen aufrufen, z. B. Regex.Match(String, String). Bei dieser Methode ist die Instanziierung des Objekts eines regulären Ausdrucks nicht erforderlich.
Sie können ein Regex-Objekt instanziieren und eine Instanzmethode für den Musterabgleich eines interpretierten regulären Ausdrucks aufrufen. Dies ist die Standardmethode zum Binden der Engine für reguläre Ausdrücke an das Muster eines regulären Ausdrucks. Das Ergebnis tritt ein, wenn ein Regex-Objekt ohne ein
options
-Argument instanziiert wird, das das Compiled-Flag beinhaltet.Sie können ein Regex-Objekt instanziieren und eine Instanzmethode für Musterübereinstimmungen eines quellengenerierten regulären Ausdrucks aufrufen. Diese Technik wird in den meisten Fällen empfohlen. Platzieren Sie dazu das GeneratedRegexAttribute-Attribut auf eine partielle Methode, die
Regex
zurückgibt.Sie können ein Regex-Objekt instanziieren und eine Instanzmethode für Musterübereinstimmungen eines kompilierten regulären Ausdrucks aufrufen. Objekte regulärer Ausdrücke stellen kompilierte Muster dar, wenn ein Regex-Objekt mit einem
options
-Argument instanziiert wird, das das Compiled-Flag beinhaltet.
Die Art und Weise, wie Sie Methoden für den Abgleich regulärer Ausdrücke aufrufen, kann sich auf die Leistung Ihrer Anwendung auswirken. In den folgenden Abschnitten wird erläutert, wann statische Methodenaufrufe, quellengenerierte reguläre Ausdrücke, interpretierte reguläre Ausdrücke und kompilierte reguläre Ausdrücke verwendet werden sollen, um die Leistung Ihrer Anwendung zu verbessern.
Wichtig
Die Art des Methodenaufrufs (statisch, interpretiert, quellengeneriert, kompiliert) wirkt sich auf die Leistung aus, wenn ein regulärer Ausdruck wiederholt in Methodenaufrufen verwendet wird oder wenn eine Anwendung umfassenden Gebrauch von Objekten regulärer Ausdrücke macht.
Statische reguläre Ausdrücke
Statische Methoden für reguläre Ausdrücke werden als Alternative zum wiederholten Instanziieren eines Objekts für reguläre Ausdrücke mit demselben regulären Ausdruck empfohlen. Im Gegensatz zu Mustern regulärer Ausdrücke, die von Objekten regulärer Ausdrücke verwendet werden, werden von der Engine für reguläre Ausdrücke entweder die Vorgangscodes (opcodes) oder die kompilierte CIL (Common Intermediate Language) aus Mustern, die in statischen Methodenaufrufen verwendet werden, intern zwischengespeichert.
Beispielsweise ruft ein Ereignishandler häufig eine andere Methode auf, um Benutzereingaben zu überprüfen. Dieses Beispiel wird im folgenden Code gezeigt, in dem das Click-Ereignis eines Button-Steuerelements verwendet wird, um eine Methode namens IsValidCurrency
aufzurufen, die überprüft, ob die Benutzer*innen ein Währungssymbol gefolgt von mindestens einer Dezimalziffer eingegeben haben.
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
Eine ineffiziente Implementierung der IsValidCurrency
-Methode wird im folgenden Beispiel gezeigt:
Hinweis
Bei jedem Methodenaufruf wird ein Regex-Objekt mit demselben Muster erneut instanziiert. Dies bedeutet wiederum, dass das Muster für reguläre Ausdrücke bei jedem Aufruf der Methode erneut kompiliert werden muss.
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
Sie sollten den obigen ineffizienten Code durch einen Aufruf der statischen Regex.IsMatch(String, String)-Methode ersetzen. Durch diesen Ansatz entfällt die Notwendigkeit, jedes Mal ein Regex-Objekt zu instanziieren, wenn Sie eine Methode für den Musterabgleich aufrufen möchten. Außerdem kann die Engine für reguläre Ausdrücke eine kompilierte Version des regulären Ausdrucks aus ihrem Cache abrufen.
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
Standardmäßig werden die letzten 15 zuletzt verwendeten statischen Muster für reguläre Ausdrücke zwischengespeichert. Für Anwendungen, die eine größere Anzahl von zwischengespeicherten statischen regulären Ausdrücken benötigen, kann die Größe des Caches durch Festlegen der Regex.CacheSize-Eigenschaft angepasst werden.
Der in diesem Beispiel verwendete reguläre Ausdruck \p{Sc}+\s*\d+
überprüft, ob die Eingabezeichenfolge ein Währungssymbol und mindestens eine Dezimalzahl enthält. Das Muster ist wie in der folgenden Tabelle gezeigt definiert:
Muster | BESCHREIBUNG |
---|---|
\p{Sc}+ |
Übereinstimmung mit mindestens einem Zeichen aus der Kategorie „Unicode-Symbol, Währung“. |
\s* |
Übereinstimmung mit 0 (null) oder mehr Leerzeichen. |
\d+ |
Übereinstimmung mit einer oder mehreren Dezimalzahlen. |
Interpretiert im Vergleich zu quellengenerierten im Vergleich zu kompilierten regulären Ausdrücken
Muster für reguläre Ausdrücke, die nicht durch Angabe der Compiled-Option an die Engine für reguläre Ausdrücke gebunden sind, werden interpretiert. Wenn ein Objekt für reguläre Ausdrücke instanziiert wird, konvertiert die Engine für reguläre Ausdrücke den regulären Ausdruck in einen Satz von Operationscodes. Beim Aufrufen einer Instanzmethode werden die Vorgangscodes in CIL konvertiert und vom JIT-Compiler ausgeführt. Wenn eine statische Methode für reguläre Ausdrücke aufgerufen und der reguläre Ausdruck nicht im Cache gefunden wird, konvertiert die Engine für reguläre Ausdrücke den regulären Ausdruck ebenfalls in einen Satz von Vorgangscodes und speichert diese im Cache. Dann werden diese Vorgangscodes in CIL konvertiert, damit der JIT-Compiler sie ausführen kann. Interpretierte reguläre Ausdrücke reduzieren die Ladezeit, führen aber zu einer langsameren Ausführungszeit. Ihre Verwendung empfiehlt sich aufgrund dieses Prozesses vor allem dann, wenn der reguläre Ausdruck in einer kleinen Anzahl von Methodenaufrufen verwendet wird oder wenn die genaue Anzahl von Aufrufen von Methoden für reguläre Ausdrücke zwar unbekannt, jedoch erwartungsgemäß niedrig ist. Wenn die Anzahl der Methodenaufrufe zunimmt, wird die durch die kürzere Startzeit erzielte Leistungssteigerung durch die langsamere Ausführungsgeschwindigkeit wieder ausgeglichen.
Muster für reguläre Ausdrücke, die durch Angabe der Compiled-Option an die Engine für reguläre Ausdrücke gebunden sind, werden kompiliert. Wenn also ein Objekt für reguläre Ausdrücke instanziiert oder eine statische Methode für reguläre Ausdrücke aufgerufen wird und der reguläre Ausdruck nicht im Cache gefunden wird, konvertiert die Engine für reguläre Ausdrücke den regulären Ausdruck in einen vorläufigen Satz von Vorgangscodes. Diese Codes werden dann in CIL konvertiert. Wenn eine Methode aufgerufen wird, führt der JIT-Compiler die CIL aus. Im Gegensatz zu interpretierten regulären Ausdrücken erhöhen kompilierte reguläre Ausdrücke die Startzeit. Einzelne Methoden für Musterübereinstimmungen werden aber schneller ausgeführt. Dadurch vergrößert sich der Leistungsvorteil, der sich aus dem Kompilieren eines regulären Ausdrucks ergibt, relativ zur Anzahl der aufgerufenen Methoden für reguläre Ausdrücke.
Muster für reguläre Ausdrücke, die über das Randsteuerelement einer Methode, die Regex
mit dem Attribut GeneratedRegexAttribute an das Modul für reguläre Ausdrücke gebunden zurückgibt, werden quellengeneriert. Der Quellgenerator, der an den Compiler anbindet, gibt als C#-Code eine benutzerdefinierte, von Regex
abgeleitete Implementierung mit Logik aus, die dem entspricht, was RegexOptions.Compiled
in CIL emittiert. Sie erhalten alle Durchsatzleistungsvorteile von RegexOptions.Compiled
(tatsächlich sogar noch mehr) sowie die Startvorteile von Regex.CompileToAssembly
, aber ohne die Komplexität von CompileToAssembly
. Die ausgegebene Quelle ist Teil Ihres Projekts, was bedeutet, dass sie auch mühelos angezeigt und debuggt werden kann.
Zusammenfassend empfehlen wir Folgendes:
- Verwenden Sie interpretierte reguläre Ausdrücke, wenn Sie reguläre Ausdrucksmethoden mit einem bestimmten regulären Ausdruck relativ selten aufrufen.
- Verwenden Sie quellengenerierte reguläre Ausdrücke, wenn Sie
Regex
in C#-Argumenten verwenden, die zur Kompilierungszeit bekannt sind, und Sie einen bestimmten regulären Ausdruck relativ häufig verwenden. - Verwenden Sie kompilierte reguläre Ausdrücke, wenn Sie reguläre Ausdrücke mit einem bestimmten regulären Ausdruck relativ häufig aufrufen und .NET 6 oder eine frühere Version verwenden.
Es ist schwierig, den genauen Punkt zu ermitteln, an dem die langsameren Ausführungsgeschwindigkeiten interpretierter regulärer Ausdrücke gegenüber ihren reduzierten Startzeitgewinnen überwiegen. Es ist auch schwierig, den Punkt zu ermitteln, bei dem die langsameren Startzeiten der quellengenerierten oder kompilierten regulären Ausdrücke gegenüber ihren schnelleren Ausführungsgeschwindigkeiten überwiegen. Es hängt von verschiedenen Faktoren ab, z. B. der Komplexität des regulären Ausdrucks und den spezifischen verarbeiteten Daten. Um zu ermitteln, welche regulären Ausdrücke die beste Leistung für Ihr bestimmtes Anwendungsszenario bieten, können Sie die Stopwatch-Klasse verwenden, um ihre Ausführungszeiten zu vergleichen.
Im folgenden Beispiel wird die Leistung von kompilierten, quellgenerierten und interpretierten regulären Ausdrücken beim Lesen der ersten 10 Sätze und beim Lesen aller Sätze im Text von William D. Guthries Magna Carta and Other Addresses verglichen. Die Ausgabe im Beispiel zeigt: Wenn nur zehn Aufrufe von Methoden für den Abgleich regulärer Ausdrücke erfolgen, bietet ein interpretierter oder quellengenerierter regulärer Ausdruck eine bessere Leistung als ein kompilierter regulärer Ausdruck. Ein kompilierter regulärer Ausdruck bietet jedoch die bessere Leistung bei vielen Aufrufen (in diesem Fall über 13.000).
const string Pattern = @"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]";
static readonly HttpClient s_client = new();
[GeneratedRegex(Pattern, RegexOptions.Singleline)]
private static partial Regex GeneratedRegex();
public async static Task RunIt()
{
Stopwatch sw;
Match match;
int ctr;
string text =
await s_client.GetStringAsync("https://www.gutenberg.org/cache/epub/64197/pg64197.txt");
// Read first ten sentences with interpreted regex.
Console.WriteLine("10 Sentences with Interpreted Regex:");
sw = Stopwatch.StartNew();
Regex int10 = new(Pattern, RegexOptions.Singleline);
match = int10.Match(text);
for (ctr = 0; ctr <= 9; ctr++)
{
if (match.Success)
// Do nothing with the match except get the next match.
match = match.NextMatch();
else
break;
}
sw.Stop();
Console.WriteLine(" {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
*/
Das im Beispiel verwendete Muster für reguläre Ausdrücke, \b(\w+((\r?\n)|,?\s))*\w+[.?:;!]
, ist wie in der folgenden Tabelle gezeigt definiert:
Muster | Beschreibung |
---|---|
\b |
Der Vergleich beginnt an einer Wortgrenze. |
\w+ |
Übereinstimmung mit einem oder mehreren Wortzeichen. |
(\r?\n)|,?\s) |
Übereinstimmung mit keinem (null) oder einem Wagenrücklaufzeichen gefolgt von einem Zeilenvorschubzeichen oder keinem (null) oder einem Komma gefolgt von einem Leerzeichen. |
(\w+((\r?\n)|,?\s))* |
Übereinstimmung mit keinem (null) oder mehreren Vorkommen eines oder mehrerer Wortzeichen gefolgt von keinem (null) oder einem Wagenrücklaufzeichen und einem Zeilenvorschubzeichen oder von keinem (null) oder einem Komma gefolgt von einem Leerzeichen. |
\w+ |
Übereinstimmung mit einem oder mehreren Wortzeichen. |
[.?:;!] |
Übereinstimmung mit einem Punkt, Fragezeichen, Doppelpunkt, Semikolon oder Ausrufezeichen. |
Steuern der Rückverfolgung
Normalerweise bewegt sich die Engine für reguläre Ausdrücke für den Vergleich mit einem regulären Ausdrucksmuster linear durch eine Eingabezeichenfolge. Wenn jedoch unbestimmte Quantifizierer, z. B. *
, +
oder ?
in einem Muster für reguläre Ausdrücke verwendet werden, gibt die Engine für reguläre Ausdrücke möglicherweise einen Teil der erfolgreichen Teilübereinstimmungen auf und kehrt zu einem zuvor gespeicherten Zustand zurück, um nach einer erfolgreichen Übereinstimmung mit dem gesamten Muster zu suchen. Dieser Prozess wird als Rückverfolgung bezeichnet.
Tipp
Weitere Informationen zur Rückverfolgung finden Sie unter Einzelheiten zum Verhalten regulärer Ausdrücke und Rückverfolgung. Ausführliche Besprechungen der Rückverfolgung finden Sie in den Blogbeiträgen Verbesserungen regulärer Ausdrücke in .NET 7 und Optimieren der Leistung regulärer Ausdrücke.
Durch die Unterstützung des Zurückverfolgens werden reguläre Ausdrücke leistungsstark und flexibel. Außerdem wird die Steuerung der Ausführung der Engine für reguläre Ausdrücke in die Hände der Entwickler von regulären Ausdrücken gelegt. Entwickler sind sich dieser Verantwortung oft nicht bewusst und verwenden die Rückverfolgung falsch oder übermäßig. Dies ist einer der Hauptfaktoren für die Beeinträchtigung der Leistung von regulären Ausdrücken. Im ungünstigsten Fall kann sich die Ausführungszeit für jedes zusätzliche Zeichen in der Eingabezeichenfolge verdoppeln. Bei übermäßiger Verwendung der Rückverfolgung ist es tatsächlich leicht, eine programmatische Entsprechung einer Endlosschleife zu erstellen, wenn die Eingabe fast mit dem Muster für reguläre Ausdrücke übereinstimmt. Für die Verarbeitung einer relativ kurzen Eingabezeichenfolge kann die Engine für regulärer Ausdrücke mehrere Stunden oder sogar Tage brauchen.
Leistungseinbußen bei Anwendungen kommen häufig vor, wenn die Rückverfolgung verwendet wird, obwohl diese für einen Abgleich nicht erforderlich ist. Beispielsweise gleicht der reguläre Ausdruck \b\p{Lu}\w*\b
wie in der folgenden Tabelle dargestellt alle Wörter ab, die mit einem Großbuchstaben beginnen:
Muster | Beschreibung |
---|---|
\b |
Der Vergleich beginnt an einer Wortgrenze. |
\p{Lu} |
Übereinstimmung mit einem Großbuchstaben. |
\w* |
Übereinstimmung mit keinem (null) oder mehreren Wortzeichen. |
\b |
Der Vergleich endet an einer Wortgrenze. |
Da eine Wortgrenze weder mit einem Wortzeichen identisch noch eine Teilmenge eines Wortzeichens ist, ist es nicht möglich, dass die Engine für reguläre Ausdrücke beim Abgleichen von Wortzeichen eine Wortgrenze überschreitet. Bei diesem regulären Ausdruck kann die Rückverfolgung daher nie zum Gesamterfolg eines Abgleichs beitragen. Sie kann lediglich die Leistung beeinträchtigen, da die Engine für reguläre Ausdrücke gezwungen wird, den Zustand für jeden erfolgreichen vorläufigen Abgleich eines Wortzeichens zu speichern.
Wenn Sie feststellen, dass die Rückverfolgung nicht nötig ist, können Sie sie auf verschiedene Arten deaktivieren:
durch Festlegen der RegexOptions.NonBacktracking-Option (eingeführt in .NET 7). Weitere Informationen finden Sie unter Nicht zurückverfolgender Modus.
mithilfe des
(?>subexpression)
-Sprachelements, das als atomische Gruppe bezeichnet wird. Im folgenden Beispiel wird eine Eingabezeichenfolge unter Verwendung von zwei regulären Ausdrücken analysiert. Der erste,\b\p{Lu}\w*\b
, beruht auf Rückverfolgung. Der zweite,\b\p{Lu}(?>\w*)\b
, deaktiviert die Rückverfolgung. Wie die Ausgabe im Beispiel zeigt, liefern beide dasselbe Ergebnis: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 vielen Fällen ist das Zurückverfolgen wichtig, um ein Muster für reguläre Ausdrücke mit dem Eingabetext abzugleichen. Eine übermäßige Rückverfolgung kann jedoch die Leistung erheblich beeinträchtigen und den Eindruck erwecken, dass eine Anwendung nicht mehr reagiert. Dieses Problem tritt insbesondere dann auf, wenn Quantifizierer geschachtelt sind und der Text, der dem äußeren Teilausdruck entspricht, eine Teilmenge des Textes ist, der dem inneren Teilausdruck entspricht.
Warnung
Vermeiden Sie übermäßige Rückverfolgung, und verwenden Sie außerdem die Timeoutfunktion, um sicherzustellen, dass die Leistung von regulären Ausdrücken nicht zu sehr durch übermäßige Rückverfolgung beeinträchtigt wird. Weitere Informationen finden Sie im Abschnitt Verwenden von Timeoutwerten.
Beispielsweise soll das Muster für reguläre Ausdrücke ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$
einer Teilenummer entsprechen, die aus mindestens einem alphanumerischen Zeichen besteht. Alle zusätzlichen Zeichen können aus einem alphanumerischen Zeichen, einem Bindestrich, einem Unterstrich oder einem Punkt bestehen. Das letzte Zeichen muss jedoch alphanumerisch sein. Ein Dollarzeichen beendet die Teilenummer. In einigen Fällen kann dieses Muster für reguläre Ausdrücke eine schlechte Leistung aufweisen, da die Quantifizierer geschachtelt sind und der Teilausdruck [0-9A-Z]
eine Teilmenge des Teilausdrucks [-.\w]*
ist.
In diesen Fällen können Sie die Leistung regulärer Ausdrücke optimieren, indem Sie die geschachtelten Quantifizierer entfernen und den äußeren Teilausdruck durch eine Lookahead- oder Lookbehindassertion mit einer Breite von 0 ersetzen. Lookahead- und Lookbehindassertionen sind Anker. Sie bewegen nicht den Mauszeiger in der Eingabezeichenfolge, sondern überprüfen in Vorwärts- bzw. Rückwärtsrichtung, ob eine bestimmte Bedingung erfüllt ist. Beispielsweise kann der reguläre Ausdruck für die Teilenummer wie folgt umgeschrieben werden: ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$
. Dieses Muster für reguläre Ausdrücke ist wie in der folgenden Tabelle gezeigt definiert:
Muster | Beschreibung |
---|---|
^ |
Beginnt den Vergleich am Anfang der Eingabezeichenfolge. |
[0-9A-Z] |
Übereinstimmung mit einem alphanumerischen Zeichen. Die Teilenummer muss aus mindestens diesem Zeichen bestehen. |
[-.\w]* |
Übereinstimmung mit keinem oder mehreren Vorkommen eines beliebigen Wortzeichens, eines Bindestrichs oder eines Punkts. |
\$ |
Übereinstimmung mit einem Dollarzeichen. |
(?<=[0-9A-Z]) |
Lookbehindüberprüfung für das beendende Dollarzeichen, um sicherzustellen, dass das vorherige Zeichen alphanumerisch ist. |
$ |
Ende des Abgleichs am Ende der Eingabezeichenfolge. |
Im folgenden Beispiel wird die Verwendung dieses regulären Ausdrucks zum Abgleichen eines Arrays veranschaulicht, das mögliche Teilenummern enthält:
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.
Die Sprache für reguläre Ausdrücke in .NET beinhaltet die folgenden Sprachelemente, die Sie verwenden können, um geschachtelte Quantifizierer zu vermeiden. Weitere Informationen finden Sie unter Gruppierungskonstrukte.
Sprachelement | Beschreibung |
---|---|
(?= subexpression ) |
Positives Lookahead mit einer Breite von 0. Lookaheadüberprüfung für die aktuelle Position, um zu ermitteln, ob subexpression mit der Eingabezeichenfolge übereinstimmt. |
(?! subexpression ) |
Negatives Lookahead mit einer Breite von 0. Lookaheadüberprüfung für die aktuelle Position, um zu ermitteln, ob subexpression nicht mit der Eingabezeichenfolge übereinstimmt. |
(?<= subexpression ) |
Positives Lookbehind mit einer Breite von 0. Lookbehindüberprüfung für die aktuelle Position, um zu ermitteln, ob subexpression mit der Eingabezeichenfolge übereinstimmt. |
(?<! subexpression ) |
Negatives Lookbehind mit einer Breite von 0. Lookbehindüberprüfung für die aktuelle Position, um zu ermitteln, ob subexpression nicht mit der Eingabezeichenfolge übereinstimmt. |
Verwenden von Timeoutwerten
Wenn Ihre regulären Ausdrücke Eingaben verarbeiten, die annähernd mit dem Muster des regulären Ausdrucks übereinstimmen, wird häufig übermäßige Rückverfolgung verwendet. Dies beeinträchtigt die Leistung signifikant. Wägen Sie die Verwendung der Rückverfolgung sorgfältig ab, testen Sie den regulären Ausdruck mit fast übereinstimmenden Eingaben, und legen Sie außerdem immer einen Timeoutwert fest, um die Beeinträchtigung durch übermäßige Rückverfolgung zu minimieren.
Das Timeoutintervall des regulären Ausdrucks definiert den Zeitraum bis zum Timeout, in dem die Engine für reguläre Ausdrücke nach einer einzelnen Übereinstimmung sucht, bevor ein Timeout erfolgt. Abhängig vom Muster des regulären Ausdrucks und dem Eingabetext kann die Ausführungszeit das angegebene Timeoutintervall überschreiten, es wird jedoch nicht mehr Zeit für die Rückverfolgung aufgewendet als das angegebene Timeoutintervall. Das Standardtimeoutintervall ist Regex.InfiniteMatchTimeout, d. h. für den regulären Ausdruck erfolgt kein Timeout. Sie können diesen Wert folgendermaßen überschreiben und ein Timeoutintervall definieren:
Rufen Sie den Regex(String, RegexOptions, TimeSpan)-Konstruktor auf, um einen Timeoutwert anzugeben, wenn Sie ein Regex-Objekt instanziieren.
Rufen Sie eine statische Methode für den Musterabgleich auf (z. B. Regex.Match(String, String, RegexOptions, TimeSpan) oder Regex.Replace(String, String, String, RegexOptions, TimeSpan)), die einen
matchTimeout
-Parameter enthält.Legen Sie einen prozessweiten oder AppDomain-weiten Wert mit Code wie
AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromMilliseconds(100));
fest.
Wenn Sie ein Timeoutintervall definiert haben und bis zum Ende dieses Intervalls keine Übereinstimmung gefunden wird, löst die Methode des regulären Ausdrucks eine RegexMatchTimeoutException-Ausnahme aus. In Ihrem Ausnahmehandler können Sie festlegen, dass der Abgleich mit einem längeren Timeoutintervall wiederholt wird, dass der Versuch abgebrochen und davon ausgegangen wird, dass keine Übereinstimmung vorhanden ist, oder dass der Versuch abgebrochen und die Ausnahmeinformationen für eine zukünftige Analyse protokolliert werden.
Im folgenden Beispiel wird eine GetWordData
-Methode definiert, die einen regulären Ausdruck mit einem Timeoutintervall von 350 Millisekunden instanziiert, um für ein Textdokument die Anzahl der Wörter und die durchschnittliche Anzahl der Zeichen pro Wort zu berechnen. Wenn ein Timout für den entsprechenden Vorgang auftritt, wird das Timeoutintervall um 350 Millisekunden erhöht und das Regex-Objekt erneut instanziiert. Wenn das neue Timeoutintervall 1 Sekunde übersteigt, löst die Methode die Ausnahme erneut für den Aufrufer aus.
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
Erfassungen nur bei Bedarf
Reguläre Ausdrücke in .NET unterstützen Gruppierungskonstrukte, mit denen Sie ein Muster für reguläre Ausdrücke in einem Teilausdruck oder mehreren Teilausdrücken gruppieren können. Die am häufigsten verwendeten Gruppierungskonstrukte in der .NET-Sprache für reguläre Ausdrücke sind (
Teilausdruck)
zum Definieren einer nummerierten Erfassungsgruppe und (?<
Name>
Teilausdruck)
zum Definieren einer benannten Erfassungsgruppe. Gruppierungskonstrukte sind wichtig für das Erstellen von Rückverweisen und für das Definieren eines Teilausdrucks, auf den ein Quantifizierer angewendet wird.
Die Verwendung dieser Sprachelemente hat jedoch auch Nachteile. Sie führen dazu, dass das von der GroupCollection-Eigenschaft zurückgegebene Match.Groups-Objekt mit den neuesten unbenannten oder benannten Erfassungen aufgefüllt wird. Wenn ein einzelnes Gruppierungskonstrukt mehrere Teilzeichenfolgen in der Eingabezeichenfolge erfasst hat, wird auch das von der Group.Captures-Eigenschaft einer bestimmten Erfassungsgruppe zurückgegebene CaptureCollection-Objekt mit mehreren Capture-Objekten aufgefüllt.
Häufig werden Gruppierungskonstrukte nur in einem regulären Ausdruck verwendet, damit Quantifizierer auf sie angewendet werden können. Die von diesen Teilausdrücken erfassten Gruppen werden später nicht verwendet. Beispielsweise soll der reguläre Ausdruck \b(\w+[;,]?\s?)+[.?!]
einen vollständigen Satz erfassen. In der folgenden Tabelle werden die Sprachelemente in diesem regulären Ausdrucksmuster und ihre Auswirkungen auf die Match- und Match.Groups-Auflistungen des Group.Captures-Objekts beschrieben:
Muster | Beschreibung |
---|---|
\b |
Der Vergleich beginnt an einer Wortgrenze. |
\w+ |
Übereinstimmung mit einem oder mehreren Wortzeichen. |
[;,]? |
Übereinstimmung mit keinem (null) oder einem Komma oder Semikolon. |
\s? |
Übereinstimmung mit keinem (null) oder einem Leerzeichen. |
(\w+[;,]?\s?)+ |
Übereinstimmung mit einem oder mehreren Vorkommen eines oder mehrerer Wortzeichen gefolgt von einem optionalen Komma oder Semikolon gefolgt von einem optionalen Leerzeichen. Dieses Muster definiert die erste Erfassungsgruppe. Diese ist erforderlich, damit die Kombination mehrerer Wortzeichen (d. h. ein Wort) gefolgt von einem optionalen Interpunktionszeichen wiederholt wird, bis die Engine für reguläre Ausdrücke das Ende eines Satzes erreicht. |
[.?!] |
Übereinstimmung mit einem Punkt, Fragezeichen oder Ausrufezeichen. |
Wie im folgenden Beispiel gezeigt, werden das GroupCollection-Objekt und das CaptureCollection-Objekt mit Erfassungen aus der Übereinstimmungssuche aufgefüllt, wenn eine Übereinstimmung gefunden wird. In diesem Fall wird die Erfassungsgruppe (\w+[;,]?\s?)
angegeben, damit der +
-Quantifizierer darauf angewendet werden kann, wodurch das Muster für reguläre Ausdrücke Übereinstimmungen für jedes Wort in einem Satz erfassen kann. Andernfalls würde es mit dem letzten Wort in einem Satz abgeglichen werden.
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.
Wenn Sie Teilausdrücke nur verwenden, um Quantifizierer darauf anzuwenden, und den erfassten Text nicht benötigen, sollten Sie Gruppenerfassungen deaktivieren. Zum Beispiel verhindert das Sprachelement (?:subexpression)
, dass die Gruppe, auf die es angewendet wird, übereinstimmende Teilzeichenfolgen erfasst. Im folgenden Beispiel wird das Muster eines regulären Ausdrucks aus dem vorherigen Beispiel in \b(?:\w+[;,]?\s?)+[.?!]
geändert. Wie die Ausgabe im Beispiel zeigt, wird dadurch verhindert, dass die Engine für reguläre Ausdrücke die GroupCollection- und CaptureCollection-Auflistungen auffüllt:
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.
Erfassungen können Sie auf eine der folgenden Arten deaktivieren:
Verwenden Sie das Sprachelement
(?:subexpression)
. Dieses Element verhindert die Erfassung übereinstimmender Teilzeichenfolge in der Gruppe, auf die es angewendet wird. Die Erfassung von Teilzeichenfolgen in geschachtelten Gruppen wird dadurch nicht deaktiviert.Verwenden Sie die ExplicitCapture-Option. Diese Option deaktiviert alle unbenannten oder impliziten Erfassungen im Muster für reguläre Ausdrücke. Wenn Sie diese Option verwenden, können nur Teilzeichenfolgen erfasst werden, die mit benannten Gruppen übereinstimmen, die mit dem Sprachelement
(?<name>subexpression)
definiert wurden. Das ExplicitCapture-Flag kann an denoptions
-Parameter eines Regex-Klassenkonstruktors oder an denoptions
-Parameter einer statischen Regex Übereinstimmungsmethode übergeben werden.Verwenden Sie die
n
-Option im Sprachelement(?imnsx)
. Diese Option deaktiviert alle unbenannten oder impliziten Erfassungen ab dem Punkt im Muster für reguläre Ausdrücke, an dem das Element erscheint. Erfassungen werden entweder bis zum Ende des Musters oder so lange deaktiviert, bis die(-n)
-Option unbenannte oder implizite Erfassungen aktiviert. Weitere Informationen finden Sie unter Verschiedene Konstrukte.Verwenden Sie die
n
-Option im Sprachelement(?imnsx:subexpression)
. Diese Option deaktiviert alle unbenannten oder impliziten Erfassungen insubexpression
. Erfassungen von allen unbenannten oder impliziten geschachtelten Erfassungsgruppen werden ebenfalls deaktiviert.
Threadsicherheit
Die Regex-Klasse selbst ist threadsicher und nicht änderbar (schreibgeschützt). Regex
-Objekte können also in jedem Thread erzeugt und von mehreren Threads gemeinsam genutzt werden. Übereinstimmende Methoden können von jedem Thread aufgerufen werden und ändern keinen globalen Zustand.
Ergebnisobjekte (Match
und MatchCollection
), die von Regex
zurückgegeben werden, sollten jedoch in einem einzelnen Thread verwendet werden. Obwohl viele dieser Objekte logisch nicht änderbar sind, können ihre Implementierungen die Berechnung einiger Ergebnisse zur Verbesserung der Leistung verzögern. Daher müssen Aufrufer den Zugriff darauf serialisieren.
Wenn Regex
-Ergebnisobjekte in mehreren Threads gemeinsam genutzt werden müssen, können diese Objekte durch den Aufruf ihrer synchronisierten Methoden in threadsichere Instanzen konvertiert werden. Mit Ausnahme von Enumeratoren sind alle Klassen für reguläre Ausdrücke threadsicher oder können von einer synchronisierten Methode in threadsichere Objekte konvertiert werden.
Enumeratoren sind die einzige Ausnahme. Sie müssen Aufrufe von Sammlungsenumeratoren serialisieren. Die Regel ist, dass für eine Sammlung, die in mehr als einem Thread gleichzeitig als Enumeration verwendet werden kann, die Enumeratormethoden im Stammobjekt der Sammlung, die der Enumerator durchläuft, synchronisiert werden müssen.
Verwandte Artikel
Titel | Beschreibung |
---|---|
Einzelheiten zum Verhalten regulärer Ausdrücke | Überprüft die Implementierung der Engine für reguläre Ausdrücke in .NET. Schwerpunkt dieses Artikels ist die Flexibilität regulärer Ausdrücke. Außerdem wird die Verantwortung der Entwickler*innen erläutert, die effiziente und stabile Ausführung der Engine für reguläre Ausdrücke sicherzustellen. |
Backtracking | Erläutert die Rückverfolgung und deren Auswirkungen auf die Leistung von regulären Ausdrücken. Zudem werden Sprachelemente beschrieben, die Alternativen zum Zurückverfolgen bieten. |
Sprachelemente für reguläre Ausdrücke – Kurzübersicht | Beschreibt die Elemente der Sprache für reguläre Ausdrücke in .NET und enthält Links zu ausführlichen Dokumentationen für jedes Sprachelement. |