Note
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de changer d’annuaire.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de changer d’annuaire.
Le moteur d’expression régulière dans .NET est un outil puissant et complet qui traite le texte basé sur des correspondances de modèles plutôt que sur la comparaison et la correspondance de texte littéral. Dans la plupart des cas, il effectue une correspondance de motifs rapidement et efficacement. Toutefois, dans certains cas, le moteur d’expression régulière peut sembler être lent. Dans les cas extrêmes, il peut même sembler cesser de répondre car il traite une entrée relativement petite au cours des heures ou même des jours.
Cet article décrit certaines des meilleures pratiques que les développeurs peuvent adopter pour s’assurer que leurs expressions régulières obtiennent des performances optimales.
Avertissement
Lorsque vous utilisez System.Text.RegularExpressions pour traiter une entrée non approuvée, passez un délai d’expiration. Un utilisateur malveillant peut fournir une entrée à RegularExpressions, provoquant une attaque par déni de service. Les API d’infrastructure ASP.NET Core qui utilisent RegularExpressions passent un délai d’expiration.
Prendre en compte la source d’entrée
En général, les expressions régulières peuvent accepter deux types d’entrée : contraintes ou non contraintes. L’entrée contrainte est un texte qui provient d’une source connue ou fiable et suit un format prédéfini. L’entrée non contrainte est un texte qui provient d’une source non fiable, telle qu’un utilisateur web, et peut ne pas suivre un format prédéfini ou attendu.
Les modèles d’expression régulière sont souvent écrits pour correspondre à une entrée valide. Autrement dit, les développeurs examinent le texte qu’ils souhaitent mettre en correspondance, puis écrivent un modèle d’expression régulière qui le correspond. Les développeurs déterminent ensuite si ce modèle nécessite une correction ou une autre élaboration en le testant avec plusieurs éléments d’entrée valides. Lorsque le modèle correspond à toutes les entrées valides présumées, il est déclaré prêt pour la production et peut être inclus dans une application publiée. Cette approche rend un modèle d’expression régulière adapté aux entrées contraintes correspondantes. Toutefois, elle ne convient pas à la mise en correspondance d’une entrée non contrainte.
Pour correspondre à une entrée non contrainte, une expression régulière doit gérer trois types de texte efficacement :
- Texte qui correspond au modèle d’expression régulière.
- Texte qui ne correspond pas au modèle d’expression régulière.
- Texte qui correspond presque au modèle d’expression régulière.
Le dernier type de texte est particulièrement problématique pour une expression régulière qui a été écrite pour gérer les entrées contraintes. Si cette expression régulière s’appuie également sur un retour arrière étendu, le moteur d’expression régulière peut passer un certain temps (dans certains cas, plusieurs heures ou jours) à traiter du texte apparemment innocu.
Avertissement
L’exemple suivant utilise une expression régulière qui est sujette à un retour arrière excessif et qui est susceptible de rejeter des adresses e-mail valides. Vous ne devez pas l’utiliser dans une routine de validation par e-mail. Si vous souhaitez une expression régulière qui valide les adresses e-mail, consultez How to : Verify that Strings Are in Valid Email Format.
Par exemple, considérez une expression régulière couramment utilisée mais problématique pour valider l’alias d’une adresse e-mail. L’expression ^[0-9A-Z]([-.\w]*[0-9A-Z])*$ régulière est écrite pour traiter ce qui est considéré comme une adresse e-mail valide. Une adresse e-mail valide se compose d’un caractère alphanumérique, suivi de zéro ou plusieurs caractères pouvant être alphanumériques, points ou traits d’union. L’expression régulière doit se terminer par un caractère alphanumérique. Toutefois, comme l’illustre l’exemple suivant, bien que cette expression régulière gère facilement les entrées valides, ses performances sont inefficaces lorsqu’elle traite une entrée presque valide :
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
Comme le montre la sortie de l’exemple précédent, le moteur d’expression régulière traite l’alias de messagerie valide dans un intervalle de temps à peu près égal, quelle que soit sa longueur. En revanche, lorsque l’adresse e-mail presque valide a plus de cinq caractères, le temps de traitement est approximativement double pour chaque caractère supplémentaire dans la chaîne. Par conséquent, une chaîne de 28 caractères presque valide prend plus d’une heure pour traiter, et une chaîne de 33 caractères presque valide prend près d’un jour pour traiter.
Étant donné que cette expression régulière a été développée uniquement en tenant compte du format d’entrée à mettre en correspondance, elle ne tient pas compte de l’entrée qui ne correspond pas au modèle. Cette négligence, à son tour, peut permettre une entrée non contrôlée qui s'apparente presque au modèle d’expression régulière, ce qui peut dégrader considérablement les performances.
Pour résoudre ce problème, vous pouvez effectuer les opérations suivantes :
Lors du développement d’un modèle, vous devez tenir compte de la façon dont le retour arrière peut affecter les performances du moteur d’expression régulière, en particulier si votre expression régulière est conçue pour traiter les entrées non contraintes. Pour plus d’informations, consultez la section Prise en charge de la rétroaction.
Testez soigneusement votre expression régulière à l’aide d’une entrée non valide, quasi valide et valide. Vous pouvez utiliser Rex pour générer aléatoirement une entrée pour une expression régulière particulière. Rex est un outil d’exploration d’expressions régulières de Microsoft Research.
Gérer l’instanciation des objets de façon appropriée
Au cœur du modèle objet d’expression régulière de .NET est la classe System.Text.RegularExpressions.Regex, qui représente le moteur d’expression régulière. Souvent, le facteur le plus important qui affecte les performances des expressions régulières est la façon dont le Regex moteur est utilisé. La définition d’une expression régulière implique un couplage étroit du moteur d’expression régulière avec un modèle d’expression régulière. Ce processus de couplage est coûteux, qu’il implique l’instanciation d’un Regex objet en passant son constructeur à un modèle d’expression régulière ou en appelant une méthode statique en lui transmettant le modèle d’expression régulière et la chaîne à analyser.
Remarque
Pour une discussion détaillée sur les implications en matière de performances de l’utilisation d’expressions régulières interprétées et compilées, consultez le billet de blog Optimisation des performances des expressions régulières, partie II : Prise en charge du retour arrière.
Vous pouvez coupler le moteur d’expression régulière avec un modèle d’expression régulière spécifique, puis utiliser le moteur pour faire correspondre le texte de plusieurs façons :
Vous pouvez appeler une méthode de correspondance de modèle statique, telle que Regex.Match(String, String). Cette méthode ne nécessite pas d’instanciation d’un objet d’expression régulière.
Vous pouvez instancier un Regex objet et appeler une méthode de correspondance de modèle d’instance d’une expression régulière interprétée, qui est la méthode par défaut pour lier le moteur d’expression régulière à un modèle d’expression régulière. Cela se produit lorsqu'un objet Regex est instancié sans argument
optionsqui inclut l'indicateur Compiled.Vous pouvez instancier un objet Regex et appeler une méthode de correspondance d’instance d’une expression régulière générée par le code source. Cette technique est recommandée dans la plupart des cas. Pour ce faire, placez l’attribut GeneratedRegexAttribute sur une méthode partielle qui retourne
Regex.Vous pouvez instancier un objet Regex et appeler une méthode de correspondance de motifs d'instance d'une expression régulière compilée. Les objets d’expression régulière représentent des modèles compilés lorsqu’un Regex objet est instancié avec un
optionsargument qui inclut l’indicateur Compiled .
La façon particulière dont vous appelez des méthodes de correspondance d’expression régulière peut affecter les performances de votre application. Les sections suivantes décrivent quand utiliser des appels de méthode statique, des expressions régulières générées par la source, des expressions régulières interprétées et des expressions régulières compilées pour améliorer les performances de votre application.
Importante
La forme de l’appel de méthode (statique, interprété, généré par la source, compilé) affecte les performances si la même expression régulière est utilisée à plusieurs reprises dans les appels de méthode, ou si une application utilise largement les objets d’expression régulière.
Expressions régulières statiques
Les méthodes d’expression régulière statique sont recommandées comme alternative à l’instanciation répétée d’un objet d’expression régulière avec la même expression régulière. Contrairement aux modèles d’expression régulière utilisés par les objets d’expression régulière, les codes d’opération (opcodes) ou le langage commun (CIL) compilé à partir de modèles utilisés dans les appels de méthode statique sont mis en cache en interne par le moteur d’expression régulière.
Par exemple, un gestionnaire d’événements appelle fréquemment une autre méthode pour valider l’entrée utilisateur. Cet exemple est reflété dans le code suivant, dans lequel l’événement d’un ButtonClick contrôle est utilisé pour appeler une méthode nommée IsValidCurrency, qui vérifie si l’utilisateur a entré un symbole monétaire suivi d’au moins un chiffre décimal.
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
Une implémentation inefficace de la IsValidCurrency méthode est illustrée dans l’exemple suivant :
Remarque
Chaque appel de méthode réinstancie un Regex objet avec le même modèle. Cela signifie qu’à son tour, le modèle d’expression régulière doit être recompilé chaque fois que la méthode est appelée.
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
Vous devez remplacer le code inefficace précédent par un appel à la méthode statique Regex.IsMatch(String, String) . Cette approche élimine la nécessité d’instancier un Regex objet chaque fois que vous souhaitez appeler une méthode de correspondance de modèle et permet au moteur d’expression régulière de récupérer une version compilée de l’expression régulière à partir de son cache.
using System;
using System.Text.RegularExpressions;
public class RegexLib2
{
public static bool IsValidCurrency(string currencyValue)
{
string pattern = @"\p{Sc}+\s*\d+";
return Regex.IsMatch(currencyValue, pattern);
}
}
Imports System.Text.RegularExpressions
Public Module RegexLib
Public Function IsValidCurrency(currencyValue As String) As Boolean
Dim pattern As String = "\p{Sc}+\s*\d+"
Return Regex.IsMatch(currencyValue, pattern)
End Function
End Module
Par défaut, les 15 derniers modèles d’expression régulière statique utilisés sont mis en cache. Pour les applications qui nécessitent un plus grand nombre d’expressions régulières statiques mises en cache, la taille du cache peut être ajustée en définissant la Regex.CacheSize propriété.
L’expression \p{Sc}+\s*\d+ régulière utilisée dans cet exemple vérifie que la chaîne d’entrée a un symbole monétaire et au moins un chiffre décimal. Le modèle est défini comme indiqué dans le tableau suivant :
| Modèle | Descriptif |
|---|---|
\p{Sc}+ |
Correspond à un ou plusieurs caractères dans la catégorie Symbole Unicode, Devise. |
\s* |
Correspond à zéro, un ou plusieurs espaces blancs. |
\d+ |
Correspond à un ou plusieurs chiffres décimaux. |
Expressions régulières interprétées, générées à partir du code source, et compilées
Les modèles d’expression régulière qui ne sont pas liés au moteur d’expression régulière par le biais de la spécification de l’option Compiled sont interprétés. Lorsqu’un objet d’expression régulière est instancié, le moteur d’expression régulière convertit l’expression régulière en un ensemble de codes d’opération. Lorsqu’une méthode d’instance est appelée, les codes d’opération sont convertis en CIL et exécutés par le compilateur JIT. De même, lorsqu’une méthode d’expression régulière statique est appelée et que l’expression régulière est introuvable dans le cache, le moteur d’expression régulière convertit l’expression régulière en un ensemble de codes d’opération et les stocke dans le cache. Il convertit ensuite ces codes d’opération en CIL afin que le compilateur JIT puisse les exécuter. Les expressions régulières interprétées réduisent le temps de démarrage au coût du temps d’exécution plus lent. En raison de ce processus, ils sont mieux utilisés lorsque l’expression régulière est utilisée dans un petit nombre d’appels de méthode, ou si le nombre exact d’appels aux méthodes d’expression régulière est inconnu, mais est censé être petit. À mesure que le nombre d’appels de méthode augmente, le gain de performances du temps de démarrage réduit est dépassé par la vitesse d’exécution la plus lente.
Les modèles d’expression régulière liés au moteur d’expression régulière par le biais de la spécification de l’option Compiled sont compilés. Par conséquent, lorsqu’un objet d’expression régulière est instancié ou lorsqu’une méthode d’expression régulière statique est appelée et que l’expression régulière est introuvable dans le cache, le moteur d’expression régulière convertit l’expression régulière en un ensemble intermédiaire de codes d’opération. Ces codes sont ensuite convertis en CIL. Lorsqu’une méthode est appelée, le compilateur JIT exécute la bibliothèque CIL. Contrairement aux expressions régulières interprétées, les expressions régulières compilées augmentent le temps de démarrage, mais exécutent des méthodes de correspondance de modèles individuelles plus rapidement. Ainsi, le gain de performance résultant de la compilation de l'expression régulière augmente proportionnellement au nombre de méthodes d'expression régulière appelées.
Les modèles d’expressions régulières qui sont associés au moteur d’expression régulière par l’ornement d’une méthode retournant Regex avec l’attribut GeneratedRegexAttribute sont générés par le code source. Le générateur de source, qui se connecte au compilateur, émet une implémentation personnalisée dérivée sous forme de code C# avec une logique similaire à celle que Regex émet dans CIL. Vous bénéficiez de tous les avantages en matière de performances de débit ( RegexOptions.Compiled plus, en fait) et des avantages de démarrage de Regex.CompileToAssembly, mais sans la complexité de CompileToAssembly. La source émise fait partie de votre projet, ce qui signifie qu'elle est également facilement visible et débogable.
Pour résumer, nous vous recommandons de :
- Utilisez des expressions régulières interprétées lorsque vous appelez des méthodes d’expression régulière avec une expression régulière spécifique relativement peu fréquente.
- Utilisez des expressions régulières générées par la source si vous utilisez
Regexen C# avec des arguments connus au moment de la compilation et que vous utilisez une expression régulière spécifique relativement fréquemment. - Utilisez des expressions régulières compilées lorsque vous appelez des méthodes d’expression régulière avec une expression régulière spécifique relativement fréquemment et que vous utilisez .NET 6 ou une version antérieure.
Il est difficile de déterminer le seuil exact auquel les vitesses d’exécution plus lentes des expressions régulières interprétées l’emportent sur les gains de leur temps de démarrage réduit. Il est également difficile de déterminer le seuil auquel les temps de démarrage plus lents des expressions régulières générées ou compilées par la source l’emportent sur les gains de leurs vitesses d’exécution plus rapides. Les seuils dépendent de différents facteurs, notamment la complexité de l’expression régulière et les données spécifiques qu’il traite. Pour déterminer quelles expressions régulières offrent les meilleures performances pour votre scénario d’application particulier, vous pouvez utiliser la Stopwatch classe pour comparer leurs temps d’exécution.
L’exemple suivant compare les performances des expressions régulières compilées, générées par la source et interprétées lors de la lecture des 10 premières phrases et lors de la lecture de toutes les phrases dans le texte de La Magna Carta de William D. Guthrie et d’autres adresses. Comme le montre la sortie de l’exemple, lorsque seulement 10 appels sont effectués à des méthodes de correspondance d’expression régulière, une expression régulière interprétée ou générée par la source offre de meilleures performances qu’une expression régulière compilée. Toutefois, une expression régulière compilée offre de meilleures performances lorsqu’un grand nombre d’appels (dans ce cas, plus de 13 000) sont effectués.
const string Pattern = @"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]";
static readonly HttpClient s_client = new();
[GeneratedRegex(Pattern, RegexOptions.Singleline)]
private static partial Regex GeneratedRegex();
public async static Task RunIt()
{
Stopwatch sw;
Match match;
int ctr;
string text =
await s_client.GetStringAsync("https://www.gutenberg.org/cache/epub/64197/pg64197.txt");
// Read first ten sentences with interpreted regex.
Console.WriteLine("10 Sentences with Interpreted Regex:");
sw = Stopwatch.StartNew();
Regex int10 = new(Pattern, RegexOptions.Singleline);
match = int10.Match(text);
for (ctr = 0; ctr <= 9; ctr++)
{
if (match.Success)
// Do nothing with the match except get the next match.
match = match.NextMatch();
else
break;
}
sw.Stop();
Console.WriteLine($" {ctr} matches in {sw.Elapsed}");
// Read first ten sentences with compiled regex.
Console.WriteLine("10 Sentences with Compiled Regex:");
sw = Stopwatch.StartNew();
Regex comp10 = new Regex(Pattern,
RegexOptions.Singleline | RegexOptions.Compiled);
match = comp10.Match(text);
for (ctr = 0; ctr <= 9; ctr++)
{
if (match.Success)
// Do nothing with the match except get the next match.
match = match.NextMatch();
else
break;
}
sw.Stop();
Console.WriteLine($" {ctr} matches in {sw.Elapsed}");
// Read first ten sentences with source-generated regex.
Console.WriteLine("10 Sentences with Source-generated Regex:");
sw = Stopwatch.StartNew();
match = GeneratedRegex().Match(text);
for (ctr = 0; ctr <= 9; ctr++)
{
if (match.Success)
// Do nothing with the match except get the next match.
match = match.NextMatch();
else
break;
}
sw.Stop();
Console.WriteLine($" {ctr} matches in {sw.Elapsed}");
// Read all sentences with interpreted regex.
Console.WriteLine("All Sentences with Interpreted Regex:");
sw = Stopwatch.StartNew();
Regex intAll = new(Pattern, RegexOptions.Singleline);
match = intAll.Match(text);
int matches = 0;
while (match.Success)
{
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine($" {matches:N0} matches in {sw.Elapsed}");
// Read all sentences with compiled regex.
Console.WriteLine("All Sentences with Compiled Regex:");
sw = Stopwatch.StartNew();
Regex compAll = new(Pattern,
RegexOptions.Singleline | RegexOptions.Compiled);
match = compAll.Match(text);
matches = 0;
while (match.Success)
{
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine($" {matches:N0} matches in {sw.Elapsed}");
// Read all sentences with source-generated regex.
Console.WriteLine("All Sentences with Source-generated Regex:");
sw = Stopwatch.StartNew();
match = GeneratedRegex().Match(text);
matches = 0;
while (match.Success)
{
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine($" {matches:N0} matches in {sw.Elapsed}");
return;
}
/* The example displays output similar to the following:
10 Sentences with Interpreted Regex:
10 matches in 00:00:00.0104920
10 Sentences with Compiled Regex:
10 matches in 00:00:00.0234604
10 Sentences with Source-generated Regex:
10 matches in 00:00:00.0060982
All Sentences with Interpreted Regex:
3,427 matches in 00:00:00.1745455
All Sentences with Compiled Regex:
3,427 matches in 00:00:00.0575488
All Sentences with Source-generated Regex:
3,427 matches in 00:00:00.2698670
*/
Le modèle d’expression régulière utilisé dans l’exemple, \b(\w+((\r?\n)|,?\s))*\w+[.?:;!]est défini comme indiqué dans le tableau suivant :
| Modèle | Descriptif |
|---|---|
\b |
Commencer la correspondance à la limite d'un mot. |
\w+ |
Correspond à un ou plusieurs caractères de mot. |
(\r?\n)|,?\s) |
Met en correspondance un ou aucun retour chariot suivi d’un caractère de saut de ligne, ou une ou aucune virgule, suivie d’un espace blanc. |
(\w+((\r?\n)|,?\s))* |
Met en correspondance zéro, une ou plusieurs occurrences d’un ou plusieurs caractères alphabétiques qui sont suivis par un ou aucun retour chariot et un caractère de saut de ligne, ou par une ou aucune virgule, suivie d’un espace blanc. |
\w+ |
Correspond à un ou plusieurs caractères de mot. |
[.?:;!] |
Met en correspondance un point, un point d’interrogation, deux-points, un point-virgule ou un point d’exclamation. |
Prise en charge de la rétroaction
En règle générale, le moteur d’expression régulière utilise la progression linéaire pour parcourir une chaîne d’entrée et la comparer à un modèle d’expression régulière. Toutefois, lorsque des quantificateurs indéterminés tels que *, +et ? sont utilisés dans un modèle d’expression régulière, le moteur d’expression régulière peut abandonner une partie des correspondances partielles réussies et revenir à un état précédemment enregistré afin de rechercher une correspondance réussie pour l’ensemble du modèle. Ce processus est appelé retour arrière.
Conseil / Astuce
Pour plus d’informations sur le retour arrière, consultez Détails du comportement d’expression régulière et retour arrière. Pour obtenir des discussions détaillées sur le retour arrière, consultez les améliorations apportées aux expressions régulières dans .NET 7 et les billets de blog Optimiser les performances des expressions régulières.
La prise en charge de la rétroaction confère aux expressions régulières leur puissance et leur flexibilité. Il place également la responsabilité de contrôler l’opération du moteur d’expression régulière entre les mains des développeurs d’expressions régulières. Étant donné que les développeurs ne sont souvent pas conscients de cette responsabilité, leur mauvaise utilisation du backtracking ou leur dépendance sur un backtracking excessif joue souvent le rôle le plus significatif dans la dégradation des performances des expressions régulières. Dans un scénario pire, le temps d’exécution peut doubler pour chaque caractère supplémentaire dans la chaîne d’entrée. En fait, en utilisant le backtracking de manière excessive, il est facile de créer l'équivalent programmatique d'une boucle infinie lorsque l'entrée correspond presque au modèle d'expression régulière. Le moteur d’expression régulière peut prendre des heures ou même des jours pour traiter une chaîne d’entrée relativement courte.
Souvent, les applications subissent des pertes de performances pour l’utilisation d’un retour sur trace, alors qu’il n’est pas essentiel pour une correspondance. Par exemple, l’expression \b\p{Lu}\w*\b régulière correspond à tous les mots qui commencent par un caractère majuscule, comme le montre le tableau suivant :
| Modèle | Descriptif |
|---|---|
\b |
Commencer la correspondance à la limite d'un mot. |
\p{Lu} |
Met en correspondance une majuscule. |
\w* |
Met en correspondance zéro, un ou plusieurs caractères alphabétiques. |
\b |
Terminer la correspondance à la limite d'un mot. |
Étant donné qu'une limite de mot n'est ni identique à un caractère de mot ni un sous-ensemble de celui-ci, il est impossible que le moteur d'expressions régulières franchisse une limite de mot en cherchant à faire correspondre des caractères de mot. Cela signifie que, pour cette expression régulière, une rétroaction ne peut jamais contribuer à la réussite globale d’une correspondance. Il ne fait que dégrader les performances, puisque le moteur d'expressions régulières est forcé d'enregistrer son état pour chaque correspondance préliminaire réussie d’un caractère alphanumérique.
Si vous déterminez que le retour arrière n’est pas nécessaire, vous pouvez le désactiver de deux façons :
En définissant l’option RegexOptions.NonBacktracking (introduite dans .NET 7). Pour plus d’informations, consultez Mode de non retour sur trace.
En utilisant l’élément
(?>subexpression)de langage, appelé groupe atomique. L’exemple suivant analyse une chaîne d’entrée à l’aide de deux expressions régulières. La première,\b\p{Lu}\w*\b, utilise la rétroaction. La seconde,\b\p{Lu}(?>\w*)\b, désactive la rétroaction. Comme le montre la sortie de l’exemple, ils produisent tous les deux le même résultat :using System; using System.Text.RegularExpressions; public class BackTrack2Example { public static void Main() { string input = "This this word Sentence name Capital"; string pattern = @"\b\p{Lu}\w*\b"; foreach (Match match in Regex.Matches(input, pattern)) Console.WriteLine(match.Value); Console.WriteLine(); pattern = @"\b\p{Lu}(?>\w*)\b"; foreach (Match match in Regex.Matches(input, pattern)) Console.WriteLine(match.Value); } } // The example displays the following output: // This // Sentence // Capital // // This // Sentence // CapitalImports System.Text.RegularExpressions Module Example Public Sub Main() Dim input As String = "This this word Sentence name Capital" Dim pattern As String = "\b\p{Lu}\w*\b" For Each match As Match In Regex.Matches(input, pattern) Console.WriteLine(match.Value) Next Console.WriteLine() pattern = "\b\p{Lu}(?>\w*)\b" For Each match As Match In Regex.Matches(input, pattern) Console.WriteLine(match.Value) Next End Sub End Module ' The example displays the following output: ' This ' Sentence ' Capital ' ' This ' Sentence ' Capital
Dans de nombreux cas, le retour arrière est essentiel pour faire correspondre un modèle d’expression régulière au texte d’entrée. Toutefois, un retour arrière excessif peut dégrader gravement les performances et créer l’impression qu’une application a cessé de répondre. En particulier, ce problème se produit lorsque les quantificateurs sont imbriqués et que le texte qui correspond à la sous-expression externe est un sous-ensemble du texte qui correspond à la sous-expression interne.
Avertissement
En plus d'éviter un retour en arrière excessif, vous devez utiliser la fonctionnalité de délai d'expiration afin de vous assurer que ce retour en arrière excessif ne dégrade pas gravement les performances des expressions régulières. Pour plus d’informations, consultez la section Utiliser les valeurs de délai d’attente .
Par exemple, le modèle ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$ d’expression régulière est destiné à correspondre à un numéro de partie constitué d’au moins un caractère alphanumérique. Tous les caractères supplémentaires peuvent se composer d’un caractère alphanumérique, d’un trait d’union, d’un trait de soulignement ou d’un point, bien que le dernier caractère doit être alphanumérique. Un signe dollar termine le numéro de référence. Dans certains cas, ce modèle d’expression régulière peut présenter des performances médiocres, car les quantificateurs sont imbriqués et parce que la sous-expression [0-9A-Z] est un sous-ensemble de la sous-expression [-.\w]*.
Dans ce cas, vous pouvez optimiser les performances des expressions régulières en supprimant les quantificateurs imbriqués et en remplaçant la sous-expression externe par une assertion lookahead ou lookbehind de largeur nulle. Les assertions avant et arrière sont des ancres. Ils ne déplacent pas le pointeur dans la chaîne d’entrée, mais regardent plutôt vers l’avant ou derrière pour vérifier si une condition spécifiée est remplie. Par exemple, l’expression régulière de numéro de partie peut être réécrite en tant que ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$. Ce modèle d’expression régulière est défini comme indiqué dans le tableau suivant :
| Modèle | Descriptif |
|---|---|
^ |
Commencer la correspondance au début de la chaîne d'entrée. |
[0-9A-Z] |
Mettre en correspondance un caractère alphanumérique. Le numéro de partie doit comporter au moins ce caractère. |
[-.\w]* |
Mettre en correspondance zéro, une ou plusieurs occurrences de tout caractère alphabétique, trait d'union ou point. |
\$ |
Mettre en correspondance un signe dollar. |
(?<=[0-9A-Z]) |
Regardez derrière le signe dollar de fin pour vous assurer que le caractère précédent est alphanumérique. |
$ |
Terminer la correspondance à la fin de la chaîne d'entrée. |
L’exemple suivant illustre l’utilisation de cette expression régulière pour faire correspondre un tableau contenant les numéros de parties possibles :
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.
Le langage d’expression régulière dans .NET inclut les éléments de langage suivants que vous pouvez utiliser pour éliminer les quantificateurs imbriqués. Pour plus d’informations, consultez Constructions de regroupement.
| Élément Language | Descriptif |
|---|---|
(?=
subexpression
)
|
Préanalyse positive de largeur nulle. Anticipe la position actuelle pour déterminer si subexpression correspond à la chaîne d'entrée. |
(?!
subexpression
)
|
Préanalyse négative de largeur nulle. Anticipe la position actuelle pour déterminer si subexpression ne correspond pas à la chaîne d’entrée. |
(?<=
subexpression
)
|
Postanalyse positive de largeur nulle. Regarde derrière la position actuelle pour déterminer si subexpression correspond à la chaîne d’entrée. |
(?<!
subexpression
)
|
Postanalyse négative de largeur nulle. Effectue une postanalyse de la position actuelle pour déterminer si subexpression ne correspond pas à la chaîne d’entrée. |
Utiliser des valeurs de délai d’attente
Si vos expressions régulières traitent des entrées qui correspondent presque au modèle d’expression régulière, elles peuvent souvent s’appuyer sur un retour arrière excessif, ce qui a un impact significatif sur ses performances. En plus d’envisager soigneusement l’utilisation de la rétroaction et de tester l’expression régulière sur une entrée presque correspondante, vous devez toujours définir une valeur de délai d’attente pour garantir la réduction de l’impact d’une rétroaction excessive, le cas échéant.
L’intervalle de délai d’expiration de l’expression régulière définit la période pendant laquelle le moteur d’expression régulière recherche une correspondance unique avant d’expirer. Selon le modèle d’expression régulière et le texte d’entrée, le temps d’exécution peut dépasser l’intervalle de délai d’attente spécifié, mais il ne passe pas plus de temps à effectuer un retour arrière que l’intervalle de délai d’attente spécifié. L’intervalle de délai d’attente par défaut est Regex.InfiniteMatchTimeout, ce qui signifie que l’expression régulière n’expire pas. Vous pouvez remplacer cette valeur et définir un intervalle de délai d’attente comme suit :
Appelez le Regex(String, RegexOptions, TimeSpan) constructeur pour fournir une valeur de délai d’attente lorsque vous instanciez un Regex objet.
Appelez une méthode de correspondance de modèle statique, telle que Regex.Match(String, String, RegexOptions, TimeSpan) ou Regex.Replace(String, String, String, RegexOptions, TimeSpan), qui inclut un
matchTimeoutparamètre.Définissez une valeur à l’échelle du domaine d’application ou à l’échelle du processus avec du code tel que
AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromMilliseconds(100));.
Si vous avez défini un intervalle de délai d’attente et qu’une correspondance n’est pas trouvée à la fin de cet intervalle, la méthode d’expression régulière lève une RegexMatchTimeoutException exception. Dans votre gestionnaire d’exceptions, vous pouvez choisir de réessayer la correspondance avec un intervalle de délai d’attente plus long, d’abandonner la tentative de correspondance et de supposer qu’il n’y a pas de correspondance, ou abandonner la tentative de correspondance et journaliser les informations d’exception pour une analyse ultérieure.
L’exemple suivant définit une méthode qui instancie une GetWordData expression régulière avec un intervalle de délai d’attente de 350 millisecondes pour calculer le nombre de mots et le nombre moyen de caractères dans un mot d’un document texte. Si l’opération correspondante expire, l’intervalle de délai d’attente est augmenté de 350 millisecondes et l’objet Regex est réinstancié. Si le nouveau délai d’attente dépasse une seconde, la méthode lève de nouveau l’exception pour l’appelant.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
public class TimeoutExample
{
public static void Main()
{
RegexUtilities util = new RegexUtilities();
string title = "Doyle - The Hound of the Baskervilles.txt";
try
{
var info = util.GetWordData(title);
Console.WriteLine($"Words: {info.Item1:N0}");
Console.WriteLine($"Average Word Length: {info.Item2:N2} characters");
}
catch (IOException e)
{
Console.WriteLine($"IOException reading file '{title}'");
Console.WriteLine(e.Message);
}
catch (RegexMatchTimeoutException e)
{
Console.WriteLine($"The operation timed out after {e.MatchTimeout.TotalMilliseconds:N0} milliseconds");
}
}
}
public class RegexUtilities
{
public Tuple<int, double> GetWordData(string filename)
{
const int MAX_TIMEOUT = 1000; // Maximum timeout interval in milliseconds.
const int INCREMENT = 350; // Milliseconds increment of timeout.
List<string> exclusions = new List<string>(new string[] { "a", "an", "the" });
int[] wordLengths = new int[29]; // Allocate an array of more than ample size.
string input = null;
StreamReader sr = null;
try
{
sr = new StreamReader(filename);
input = sr.ReadToEnd();
}
catch (FileNotFoundException e)
{
string msg = String.Format("Unable to find the file '{0}'", filename);
throw new IOException(msg, e);
}
catch (IOException e)
{
throw new IOException(e.Message, e);
}
finally
{
if (sr != null) sr.Close();
}
int timeoutInterval = INCREMENT;
bool init = false;
Regex rgx = null;
Match m = null;
int indexPos = 0;
do
{
try
{
if (!init)
{
rgx = new Regex(@"\b\w+\b", RegexOptions.None,
TimeSpan.FromMilliseconds(timeoutInterval));
m = rgx.Match(input, indexPos);
init = true;
}
else
{
m = m.NextMatch();
}
if (m.Success)
{
if (!exclusions.Contains(m.Value.ToLower()))
wordLengths[m.Value.Length]++;
indexPos += m.Length + 1;
}
}
catch (RegexMatchTimeoutException e)
{
if (e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT)
{
timeoutInterval += INCREMENT;
init = false;
}
else
{
// Rethrow the exception.
throw;
}
}
} while (m.Success);
// If regex completed successfully, calculate number of words and average length.
int nWords = 0;
long totalLength = 0;
for (int ctr = wordLengths.GetLowerBound(0); ctr <= wordLengths.GetUpperBound(0); ctr++)
{
nWords += wordLengths[ctr];
totalLength += ctr * wordLengths[ctr];
}
return new Tuple<int, double>(nWords, totalLength / nWords);
}
}
Imports System.Collections.Generic
Imports System.IO
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim util As New RegexUtilities()
Dim title As String = "Doyle - The Hound of the Baskervilles.txt"
Try
Dim info = util.GetWordData(title)
Console.WriteLine("Words: {0:N0}", info.Item1)
Console.WriteLine("Average Word Length: {0:N2} characters", info.Item2)
Catch e As IOException
Console.WriteLine("IOException reading file '{0}'", title)
Console.WriteLine(e.Message)
Catch e As RegexMatchTimeoutException
Console.WriteLine("The operation timed out after {0:N0} milliseconds",
e.MatchTimeout.TotalMilliseconds)
End Try
End Sub
End Module
Public Class RegexUtilities
Public Function GetWordData(filename As String) As Tuple(Of Integer, Double)
Const MAX_TIMEOUT As Integer = 1000 ' Maximum timeout interval in milliseconds.
Const INCREMENT As Integer = 350 ' Milliseconds increment of timeout.
Dim exclusions As New List(Of String)({"a", "an", "the"})
Dim wordLengths(30) As Integer ' Allocate an array of more than ample size.
Dim input As String = Nothing
Dim sr As StreamReader = Nothing
Try
sr = New StreamReader(filename)
input = sr.ReadToEnd()
Catch e As FileNotFoundException
Dim msg As String = String.Format("Unable to find the file '{0}'", filename)
Throw New IOException(msg, e)
Catch e As IOException
Throw New IOException(e.Message, e)
Finally
If sr IsNot Nothing Then sr.Close()
End Try
Dim timeoutInterval As Integer = INCREMENT
Dim init As Boolean = False
Dim rgx As Regex = Nothing
Dim m As Match = Nothing
Dim indexPos As Integer = 0
Do
Try
If Not init Then
rgx = New Regex("\b\w+\b", RegexOptions.None,
TimeSpan.FromMilliseconds(timeoutInterval))
m = rgx.Match(input, indexPos)
init = True
Else
m = m.NextMatch()
End If
If m.Success Then
If Not exclusions.Contains(m.Value.ToLower()) Then
wordLengths(m.Value.Length) += 1
End If
indexPos += m.Length + 1
End If
Catch e As RegexMatchTimeoutException
If e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT Then
timeoutInterval += INCREMENT
init = False
Else
' Rethrow the exception.
Throw
End If
End Try
Loop While m.Success
' If regex completed successfully, calculate number of words and average length.
Dim nWords As Integer
Dim totalLength As Long
For ctr As Integer = wordLengths.GetLowerBound(0) To wordLengths.GetUpperBound(0)
nWords += wordLengths(ctr)
totalLength += ctr * wordLengths(ctr)
Next
Return New Tuple(Of Integer, Double)(nWords, totalLength / nWords)
End Function
End Class
Capturer uniquement si nécessaire
Les expressions régulières dans .NET prennent en charge les constructions de regroupement, ce qui vous permet de regrouper un modèle d’expression régulière en une ou plusieurs sous-expressions. Les constructions de regroupement les plus couramment utilisées dans le langage d’expression régulière .NET sont ( des sous-expressions), qui définit un groupe de capture numéroté et (?< unesous-expression> de nom), qui définit un groupe de capture nommé. Les constructions de regroupement sont essentielles pour créer des inférences arrière et pour définir une sous-expression à laquelle un quantificateur est appliqué.
Toutefois, l’utilisation de ces éléments de langage a un coût. Elles entraînent le remplissage de l’objet GroupCollection retourné par la propriété Match.Groups avec les captures non nommées ou nommées les plus récentes. Si une seule structure de regroupement a capturé plusieurs sous-chaînes dans la chaîne de saisie, elle remplit également l'objet CaptureCollection que la propriété Group.Captures retourne d’un groupe de capture particulier avec plusieurs objets Capture.
Souvent, les constructions de regroupement sont utilisées dans une expression régulière uniquement afin que les quantificateurs puissent être appliqués à eux. Les groupes capturés par ces sous-expressions ne sont pas utilisés ultérieurement. Par exemple, l’expression \b(\w+[;,]?\s?)+[.?!] régulière est conçue pour capturer une phrase entière. Le tableau suivant décrit les éléments de langage de ce modèle d’expression régulière et leur effet sur les collections Match et Match.Groups de l'objet Group.Captures.
| Modèle | Descriptif |
|---|---|
\b |
Commencer la correspondance à la limite d'un mot. |
\w+ |
Correspond à un ou plusieurs caractères de mot. |
[;,]? |
Correspond à zéro ou à une virgule ou un point-virgule. |
\s? |
Correspond à zéro ou à un caractère d’espace blanc. |
(\w+[;,]?\s?)+ |
Correspond à une ou plusieurs occurrences d’un ou plusieurs caractères de mot suivis d’une virgule ou d’un point-virgule facultatif suivi d’un caractère d’espace blanc facultatif. Ce modèle définit le premier groupe de capture, qui est nécessaire afin que la combinaison de plusieurs caractères de mot (autrement dit, un mot) suivie d’un symbole de ponctuation facultatif soit répétée jusqu’à ce que le moteur d’expression régulière atteigne la fin d’une phrase. |
[.?!] |
Met en correspondance un point, un point d’interrogation ou un point d’exclamation. |
Comme l’illustre l’exemple suivant, lorsqu’une correspondance est trouvée, les objets GroupCollection et CaptureCollection sont remplis avec des captures de la correspondance. Dans ce cas, le groupe (\w+[;,]?\s?) de capture existe afin que le + quantificateur puisse être appliqué à celui-ci, ce qui permet au modèle d’expression régulière de correspondre à chaque mot dans une phrase. Sinon, il correspondrait au dernier mot d’une phrase.
using System;
using System.Text.RegularExpressions;
public class Group1Example
{
public static void Main()
{
string input = "This is one sentence. This is another.";
string pattern = @"\b(\w+[;,]?\s?)+[.?!]";
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine($"Match: '{match.Value}' at index {match.Index}.");
int grpCtr = 0;
foreach (Group grp in match.Groups)
{
Console.WriteLine($" Group {grpCtr}: '{grp.Value}' at index {grp.Index}.");
int capCtr = 0;
foreach (Capture cap in grp.Captures)
{
Console.WriteLine($" Capture {capCtr}: '{cap.Value}' at {cap.Index}.");
capCtr++;
}
grpCtr++;
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// Match: 'This is one sentence.' at index 0.
// Group 0: 'This is one sentence.' at index 0.
// Capture 0: 'This is one sentence.' at 0.
// Group 1: 'sentence' at index 12.
// Capture 0: 'This ' at 0.
// Capture 1: 'is ' at 5.
// Capture 2: 'one ' at 8.
// Capture 3: 'sentence' at 12.
//
// Match: 'This is another.' at index 22.
// Group 0: 'This is another.' at index 22.
// Capture 0: 'This is another.' at 22.
// Group 1: 'another' at index 30.
// Capture 0: 'This ' at 22.
// Capture 1: 'is ' at 27.
// Capture 2: 'another' at 30.
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim input As String = "This is one sentence. This is another."
Dim pattern As String = "\b(\w+[;,]?\s?)+[.?!]"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index)
Dim grpCtr As Integer = 0
For Each grp As Group In match.Groups
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index)
Dim capCtr As Integer = 0
For Each cap As Capture In grp.Captures
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index)
capCtr += 1
Next
grpCtr += 1
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Match: 'This is one sentence.' at index 0.
' Group 0: 'This is one sentence.' at index 0.
' Capture 0: 'This is one sentence.' at 0.
' Group 1: 'sentence' at index 12.
' Capture 0: 'This ' at 0.
' Capture 1: 'is ' at 5.
' Capture 2: 'one ' at 8.
' Capture 3: 'sentence' at 12.
'
' Match: 'This is another.' at index 22.
' Group 0: 'This is another.' at index 22.
' Capture 0: 'This is another.' at 22.
' Group 1: 'another' at index 30.
' Capture 0: 'This ' at 22.
' Capture 1: 'is ' at 27.
' Capture 2: 'another' at 30.
Lorsque vous utilisez des sous-expressions uniquement pour appliquer des quantificateurs à ceux-ci et que vous n’êtes pas intéressé par le texte capturé, vous devez désactiver les captures de groupe. Par exemple, l’élément (?:subexpression) de langage empêche le groupe auquel il s’applique de capturer les sous-chaînes correspondantes. Dans l’exemple suivant, le modèle d’expression régulière de l’exemple précédent est remplacé par \b(?:\w+[;,]?\s?)+[.?!]. Comme le montre la sortie, il empêche le moteur d’expression régulière de remplir les collections GroupCollection et CaptureCollection.
using System;
using System.Text.RegularExpressions;
public class Group2Example
{
public static void Main()
{
string input = "This is one sentence. This is another.";
string pattern = @"\b(?:\w+[;,]?\s?)+[.?!]";
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine($"Match: '{match.Value}' at index {match.Index}.");
int grpCtr = 0;
foreach (Group grp in match.Groups)
{
Console.WriteLine($" Group {grpCtr}: '{grp.Value}' at index {grp.Index}.");
int capCtr = 0;
foreach (Capture cap in grp.Captures)
{
Console.WriteLine($" Capture {capCtr}: '{cap.Value}' at {cap.Index}.");
capCtr++;
}
grpCtr++;
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// Match: 'This is one sentence.' at index 0.
// Group 0: 'This is one sentence.' at index 0.
// Capture 0: 'This is one sentence.' at 0.
//
// Match: 'This is another.' at index 22.
// Group 0: 'This is another.' at index 22.
// Capture 0: 'This is another.' at 22.
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim input As String = "This is one sentence. This is another."
Dim pattern As String = "\b(?:\w+[;,]?\s?)+[.?!]"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index)
Dim grpCtr As Integer = 0
For Each grp As Group In match.Groups
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index)
Dim capCtr As Integer = 0
For Each cap As Capture In grp.Captures
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index)
capCtr += 1
Next
grpCtr += 1
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Match: 'This is one sentence.' at index 0.
' Group 0: 'This is one sentence.' at index 0.
' Capture 0: 'This is one sentence.' at 0.
'
' Match: 'This is another.' at index 22.
' Group 0: 'This is another.' at index 22.
' Capture 0: 'This is another.' at 22.
Vous pouvez désactiver les captures de l’une des manières suivantes :
Utilisez l’élément
(?:subexpression)de langage. Cet élément empêche la capture des sous-chaînes correspondantes dans le groupe auquel il s'applique. Elle ne désactive pas les captures de sous-chaînes dans les groupes imbriqués.Utilisez l'option ExplicitCapture. Elle désactive toutes les captures non nommées ou implicites dans le modèle d’expression régulière. Lorsque vous utilisez cette option, seules les sous-chaînes qui correspondent aux groupes nommés définis avec l’élément
(?<name>subexpression)de langage peuvent être capturés. L’indicateur ExplicitCapture peut être transmis auoptionsparamètre d’un Regex constructeur de classe ou auoptionsparamètre d’une Regex méthode de correspondance statique.Utilisez l’option
ndans l’élément de langage(?imnsx). Cette option désactive toutes les captures non nommées ou implicites à partir du point dans le modèle d’expression régulière à laquelle l’élément apparaît. Les captures sont désactivées jusqu’à la fin du modèle ou jusqu’à ce que l’option(-n)active des captures non nommées ou implicites. Pour plus d’informations, consultez Constructions diverses.Utilisez l’option
ndans l’élément de langage(?imnsx:subexpression). Cette option désactive toutes les captures non nommées ou implicites danssubexpression. Les captures par tous les groupes de capture imbriqués non nommés ou implicites sont également désactivées.
Sécurité des threads
La classe Regex proprement dite est thread-safe et immuable (en lecture seule). Autrement dit, Regex les objets peuvent être créés sur n’importe quel thread et partagé entre les threads ; les méthodes correspondantes peuvent être appelées à partir de n’importe quel thread et ne modifient jamais d’état global.
Toutefois, les objets de résultat (Match et MatchCollection) retournés par Regex doivent être utilisés sur un seul thread. Bien que la plupart de ces objets soient logiquement immuables, leurs implémentations peuvent retarder le calcul de certains résultats pour améliorer les performances et, par conséquent, les appelants doivent sérialiser l’accès à ces derniers.
Si vous avez besoin de partager Regex des objets de résultat sur plusieurs threads, ces objets peuvent être convertis en instances thread-safe en appelant leurs méthodes synchronisées. À l’exception des énumérateurs, toutes les classes d’expression régulière sont thread safe ou peuvent être converties en objets thread-safe par une méthode synchronisée.
Les énumérateurs sont la seule exception. Vous devez sérialiser les appels aux énumérateurs de collection. La règle est que si une collection peut être énumérée simultanément sur plusieurs threads, vous devez synchroniser les méthodes d’énumérateur sur l’objet racine de la collection parcourue par l’énumérateur.
Articles connexes
| Titre | Descriptif |
|---|---|
| Comportement détaillé des expressions régulières | Examine l’implémentation du moteur d’expression régulière dans .NET. L’article se concentre sur la flexibilité des expressions régulières et explique la responsabilité du développeur de garantir l’efficacité et la robustesse du moteur d’expression régulière. |
| Rétroaction | Explique ce qu’est le retour arrière et comment il affecte les performances des expressions régulières et examine les éléments de langage qui fournissent des alternatives au retour arrière. |
| Langage d’expression régulière - Référence rapide | Décrit les éléments du langage d’expression régulière dans .NET et fournit des liens vers une documentation détaillée pour chaque élément de langage. |