Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
O mecanismo de expressão regular no .NET é uma ferramenta poderosa e completa que processa texto com base em correspondências de padrão em vez de comparar e corresponder texto literal. Na maioria dos casos, ele executa a correspondência de padrões de forma rápida e eficiente. No entanto, em alguns casos, o mecanismo de expressão regular pode parecer lento. Em casos extremos, pode até parecer parar de responder, pois processa uma entrada relativamente pequena ao longo de horas ou até dias.
Este artigo descreve algumas das práticas recomendadas que os desenvolvedores podem adotar para garantir que suas expressões regulares alcancem o desempenho ideal.
Advertência
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer entrada para RegularExpressions
, causando um ataque de negação de serviço . APIs da estrutura ASP.NET Core que usam RegularExpressions
implementam um tempo limite.
Considere a fonte de entrada
Em geral, as expressões regulares podem aceitar dois tipos de entrada: restrita ou sem restrições. A entrada restrita é um texto que se origina de uma fonte conhecida ou confiável e segue um formato predefinido. A entrada sem restrições é um texto originado de uma fonte não confiável, como um usuário da Web, e pode não seguir um formato predefinido ou esperado.
Os padrões de expressão regular geralmente são escritos para corresponder à entrada válida. Ou seja, os desenvolvedores examinam o texto que desejam corresponder e, em seguida, escrevem um padrão de expressão regular que corresponde a ele. Em seguida, os desenvolvedores determinam se esse padrão requer correção ou elaboração adicional testando-o com vários itens de entrada válidos. Quando o padrão corresponde a todas as entradas válidas presumidas, ele é declarado pronto para produção e pode ser incluído em um aplicativo liberado. Esta abordagem torna um padrão de expressão regular adequado para lidar com entradas restritas. No entanto, isso não o torna adequado para combinar entradas sem restrições.
Para corresponder à entrada sem restrições, uma expressão regular deve lidar com três tipos de texto de forma eficiente:
- Texto que corresponde ao padrão de expressão regular.
- Texto que não corresponde ao padrão de expressão regular.
- Texto que quase corresponde ao padrão de expressão regular.
O último tipo de texto é especialmente problemático para uma expressão regular que foi escrita para lidar com entradas restritas. Se essa expressão regular também fizer uso extensivo de retrocesso, o motor de expressões regulares pode demorar um tempo excessivo (em alguns casos, muitas horas ou dias) a processar texto aparentemente inócuo.
Advertência
O exemplo a seguir usa uma expressão regular propensa a retrocesso excessivo e que provavelmente rejeitará endereços de e-mail válidos. Você não deve usá-lo em uma rotina de validação de e-mail. Se você quiser uma expressão regular que valide endereços de email, consulte Como verificar se as cadeias de caracteres estão no formato de email válido.
Por exemplo, considere uma expressão regular comumente usada, mas problemática, para validar o alias de um endereço de e-mail. A expressão ^[0-9A-Z]([-.\w]*[0-9A-Z])*$
regular é escrita para processar o que é considerado um endereço de e-mail válido. Um endereço de e-mail válido consiste em um caractere alfanumérico, seguido por zero ou mais caracteres que podem ser alfanuméricos, pontos ou hífenes. A expressão regular deve terminar com um caractere alfanumérico. No entanto, como mostra o exemplo a seguir, embora essa expressão regular manipule entradas válidas facilmente, seu desempenho é ineficiente quando está processando entradas quase válidas:
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
Como mostra a saída do exemplo anterior, o mecanismo de expressão regular processa o alias de e-mail válido aproximadamente no mesmo intervalo de tempo, independentemente de seu comprimento. Por outro lado, quando o endereço de e-mail quase válido tem mais de cinco caracteres, o tempo de processamento aproximadamente dobra para cada caractere extra na string. Portanto, uma cadeia de caracteres de 28 caracteres quase válida levaria mais de uma hora para ser processada, e uma cadeia de caracteres de 33 caracteres quase válida levaria quase um dia para ser processada.
Como essa expressão regular foi desenvolvida apenas considerando o formato de entrada a ser correspondido, ela não leva em conta a entrada que não corresponde ao padrão. Essa supervisão, por sua vez, pode permitir que uma entrada sem restrições que encaixa quase perfeitamente no padrão da expressão regular degrade o desempenho de forma significativa.
Para resolver esse problema, você pode fazer o seguinte:
Ao desenvolver um padrão, você deve considerar como o backtracking pode afetar o desempenho do mecanismo de expressão regular, particularmente se sua expressão regular for projetada para processar entrada sem restrições. Para obter mais informações, consulte a seção Assumir o controle do backtracking .
Teste completamente sua expressão regular usando entradas inválidas, quase válidas e válidas. Você pode usar Rex para gerar aleatoriamente entradas para uma expressão regular específica. Rex é uma ferramenta de exploração de expressão regular da Microsoft Research.
Gerir a instanciação de objeto adequadamente
No coração do modelo de objeto de expressão regular do .NET está a classe System.Text.RegularExpressions.Regex, que representa o mecanismo de expressão regular. Muitas vezes, o maior fator que afeta o desempenho da expressão regular é a forma como o Regex motor é usado. Definir uma expressão regular envolve acoplar firmemente o mecanismo de expressão regular com um padrão de expressão regular. Esse processo de acoplamento é caro, quer envolva instanciar um Regex objeto passando ao seu construtor um padrão de expressão regular ou chamando um método estático passando-lhe o padrão de expressão regular e a cadeia de caracteres a ser analisada.
Observação
Para obter uma discussão detalhada das implicações de desempenho do uso de expressões regulares interpretadas e compiladas, consulte a postagem do blog Otimizando o desempenho de expressões regulares, Parte II: Assumindo o controle do backtracking.
Você pode associar o mecanismo de expressão regular a um padrão de expressão regular específico e, em seguida, usar o mecanismo para corresponder ao texto de várias maneiras:
Você pode chamar um método estático de correspondência de padrões, como Regex.Match(String, String). Esse método não requer instanciação de um objeto de expressão regular.
Você pode instanciar um objeto Regex e chamar um método de correspondência de padrão de uma expressão regular interpretada, que é o método padrão para associar o mecanismo de expressão regular a um padrão. Isso resulta quando um Regex objeto é instanciado sem um
options
argumento que inclua a flag Compiled.Você pode criar um objeto Regex e chamar um método de correspondência de padrão de instância de uma expressão regular gerada a partir do código fonte. Esta técnica é recomendada na maioria dos casos. Para fazer isso, coloque o GeneratedRegexAttribute atributo em um método parcial que retorna
Regex
.Você pode instanciar um Regex objeto e chamar um método de correspondência de padrões de instância de uma expressão regular compilada. Os objetos de expressão regular representam padrões compilados quando um Regex objeto é instanciado com um
options
argumento que inclui o Compiled sinalizador.
A maneira específica na qual você chama métodos de correspondência de expressão regular pode afetar o desempenho do seu aplicativo. As seções a seguir discutem quando usar chamadas de método estático, expressões regulares geradas pela fonte, expressões regulares interpretadas e expressões regulares compiladas para melhorar o desempenho do aplicativo.
Importante
A forma da chamada de método (estática, interpretada, gerada pela fonte, compilada) afeta o desempenho se a mesma expressão regular for usada repetidamente em chamadas de método ou se um aplicativo fizer uso extensivo de objetos de expressão regular.
Expressões regulares estáticas
Os métodos de expressão regular estática são recomendados como uma alternativa à instanciação repetida de um objeto de expressão regular com a mesma expressão regular. Ao contrário dos padrões de expressão regular usados por objetos de expressão regular, os códigos de operação (opcodes) ou a linguagem intermediária comum compilada (CIL) de padrões usados em chamadas de método estático são armazenados em cache internamente pelo mecanismo de expressão regular.
Por exemplo, um manipulador de eventos frequentemente chama outro método para validar a entrada do usuário. Este exemplo é refletido no código seguinte, no qual o Button evento de um Click controlo é usado para chamar um método chamado IsValidCurrency
, que verifica se o utilizador inseriu um símbolo de moeda seguido de pelo menos um dígito decimal.
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
Uma implementação ineficiente do IsValidCurrency
método é mostrada no exemplo a seguir:
Observação
Cada chamada de método reinstancia um Regex objeto com o mesmo padrão. Isso, por sua vez, significa que o padrão de expressão regular deve ser recompilado cada vez que o método é chamado.
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
Você deve substituir o código ineficiente anterior por uma chamada para o método estático Regex.IsMatch(String, String) . Essa abordagem elimina a necessidade de instanciar um Regex objeto sempre que você deseja chamar um método de correspondência de padrões e permite que o mecanismo de expressão regular recupere uma versão compilada da expressão regular de seu 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
Por padrão, os últimos 15 padrões de expressão regular estática usados mais recentemente são armazenados em cache. Para aplicativos que exigem um número maior de expressões regulares estáticas armazenadas em cache, o tamanho do cache pode ser ajustado definindo a Regex.CacheSize propriedade.
A expressão \p{Sc}+\s*\d+
regular usada neste exemplo verifica se a cadeia de caracteres de entrada tem um símbolo de moeda e pelo menos um dígito decimal. O padrão é definido como mostrado na tabela a seguir:
Padrão | Descrição |
---|---|
\p{Sc}+ |
Corresponde a um ou mais caracteres na categoria Símbolo Unicode, Moeda. |
\s* |
Corresponde a zero ou mais caracteres de espaço em branco. |
\d+ |
Corresponde a um ou mais dígitos decimais. |
Interpretada vs. gerada pela fonte vs. expressões regulares compiladas
Os padrões de expressão regular que não estão vinculados ao mecanismo de expressão regular por meio da especificação da Compiled opção são interpretados. Quando um objeto de expressão regular é instanciado, o mecanismo de expressão regular converte a expressão regular em um conjunto de códigos de operação. Quando um método de instância é chamado, os códigos de operação são convertidos em CIL e executados pelo compilador JIT. Da mesma forma, quando um método de expressão regular estática é chamado e a expressão regular não pode ser encontrada no cache, o mecanismo de expressão regular converte a expressão regular em um conjunto de códigos de operação e os armazena no cache. Em seguida, ele converte esses códigos de operação para CIL para que o compilador JIT possa executá-los. As expressões regulares interpretadas reduzem o tempo de inicialização ao custo de um tempo de execução mais lento. Devido a esse processo, eles são melhor usados quando a expressão regular é usada em um pequeno número de chamadas de método, ou se o número exato de chamadas para métodos de expressão regular é desconhecido, mas espera-se que seja pequeno. À medida que o número de chamadas de método aumenta, o ganho de desempenho da redução do tempo de inicialização é superado pela velocidade de execução mais lenta.
Os padrões de expressão regular que estão vinculados ao mecanismo de expressão regular por meio da especificação da Compiled opção são compilados. Portanto, quando um objeto de expressão regular é instanciado, ou quando um método de expressão regular estática é chamado e a expressão regular não pode ser encontrada no cache, o mecanismo de expressão regular converte a expressão regular em um conjunto intermediário de códigos de operação. Esses códigos são então convertidos em CIL. Quando um método é chamado, o compilador JIT executa o CIL. Em contraste com as expressões regulares interpretadas, as expressões regulares compiladas aumentam o tempo de inicialização, mas executam métodos individuais de correspondência de padrões mais rapidamente. Como resultado, o benefício de desempenho resultante da compilação da expressão regular aumenta proporcionalmente ao número de métodos de expressão regular chamados.
Os padrões de expressão regular que são vinculados ao mecanismo de expressão regular por meio do adorno de um Regex
método -return com o GeneratedRegexAttribute atributo são gerados pela fonte. O gerador de código-fonte, que se conecta ao compilador, emite como código C# uma implementação derivada personalizada Regex
com lógica semelhante à que RegexOptions.Compiled
emite no CIL. Você obtém todas as vantagens de desempenho de throughput de RegexOptions.Compiled
(melhor ainda) e as vantagens iniciais do Regex.CompileToAssembly
, mas sem a complexidade do CompileToAssembly
. A fonte que é emitida integra o seu projeto, o que significa que também é facilmente visível e passível de depuração.
Para resumir, recomendamos que você:
- Use expressões regulares interpretadas quando você chamar métodos de expressão regular com uma expressão regular específica com relativamente pouca frequência.
- Use expressões regulares geradas pelo código-fonte se estiver usando
Regex
em C# com argumentos conhecidos em tempo de compilação e estiver usando uma expressão regular específica com relativa frequência. - Use expressões regulares compiladas quando você chamar métodos de expressão regular com uma expressão regular específica com relativa freqüência e estiver usando o .NET 6 ou uma versão anterior.
É difícil determinar o limite exato no qual as velocidades de execução mais lentas de expressões regulares interpretadas superam os ganhos de seu tempo de inicialização reduzido. Também é difícil determinar o limite no qual os tempos de inicialização mais lentos de expressões regulares geradas ou compiladas na fonte superam os ganhos de suas velocidades de execução mais rápidas. Os limites dependem de vários fatores, incluindo a complexidade da expressão regular e os dados específicos que ela processa. Para determinar quais expressões regulares oferecem o melhor desempenho para seu cenário de aplicativo específico, você pode usar a Stopwatch classe para comparar seus tempos de execução.
O exemplo a seguir compara o desempenho de expressões regulares compiladas, geradas pela fonte e interpretadas ao ler as primeiras 10 frases e ao ler todas as frases do texto da Magna Carta e Outros Endereços de William D. Guthrie. Como mostra a saída do exemplo, quando apenas 10 chamadas são feitas para métodos de correspondência de expressão regular, uma expressão regular interpretada ou gerada pela fonte oferece melhor desempenho do que uma expressão regular compilada. No entanto, uma expressão regular compilada oferece melhor desempenho quando um grande número de chamadas (neste caso, mais de 13.000) são feitas.
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
*/
O padrão de expressão regular usado no exemplo, \b(\w+((\r?\n)|,?\s))*\w+[.?:;!]
, é definido conforme mostrado na tabela a seguir:
Padrão | Descrição |
---|---|
\b |
Comece a partida com um limite de palavras. |
\w+ |
Corresponde a um ou mais caracteres de palavras. |
(\r?\n)|,?\s) |
Corresponde a zero ou um retorno de carro seguido por um caractere de nova linha, ou zero ou uma vírgula seguido por um caractere de espaço em branco. |
(\w+((\r?\n)|,?\s))* |
Corresponde a zero ou mais ocorrências de um ou mais caracteres de palavra que são seguidos por zero ou um retorno de carro e um caractere de nova linha, ou por zero ou uma vírgula seguida por um caractere de espaço em branco. |
\w+ |
Corresponde a um ou mais caracteres de palavras. |
[.?:;!] |
Corresponde a um ponto, ponto de interrogação, dois pontos, ponto-e-vírgula ou ponto de exclamação. |
Assuma o controle do processo de retrocesso
Normalmente, o mecanismo de expressão regular usa progressão linear para se mover através de uma cadeia de caracteres de entrada e compará-la com um padrão de expressão regular. No entanto, quando quantificadores indeterminados como *
, +
e ?
são usados em um padrão de expressão regular, o mecanismo de expressão regular pode desistir de uma parte de correspondências parciais bem-sucedidas e retornar a um estado salvo anteriormente para procurar uma correspondência bem-sucedida para todo o padrão. Esse processo é conhecido como "backtracking".
Sugestão
Para obter mais informações sobre backtracking, consulte Detalhes do comportamento de expressão regular e Backtracking. Para obter discussões aprofundadas sobre backtracking, consulte os artigos de blog Melhorias de expressão regular no .NET 7 e Otimizando o desempenho da expressão regular.
O suporte para backtracking dá às expressões regulares poder e flexibilidade. Ele também coloca a responsabilidade de controlar a operação do mecanismo de expressão regular nas mãos dos desenvolvedores de expressão regular. Como os desenvolvedores muitas vezes não estão cientes dessa responsabilidade, o uso indevido ou a dependência excessiva de backtracking geralmente desempenha o papel mais significativo na degradação do desempenho de expressões regulares. No pior cenário, o tempo de execução pode dobrar para cada caractere adicional na cadeia de caracteres de entrada. Na verdade, ao usar o backtracking excessivamente, é fácil criar o equivalente programático de um ciclo infinito se os dados de entrada quase corresponderem ao padrão de expressão regular. O mecanismo de expressão regular pode levar horas ou até dias para processar uma cadeia de caracteres de entrada relativamente curta.
Muitas vezes, os aplicativos pagam uma penalidade de desempenho por usar backtracking, mesmo que o backtracking não seja essencial para uma partida. Por exemplo, a expressão \b\p{Lu}\w*\b
regular corresponde a todas as palavras que começam com um caractere maiúsculo, como mostra a tabela a seguir:
Padrão | Descrição |
---|---|
\b |
Comece a partida com um limite de palavras. |
\p{Lu} |
Corresponde a um caractere maiúsculo. |
\w* |
Corresponde a zero ou mais caracteres de palavras. |
\b |
Termine a partida com um limite de palavras. |
Como um limite de palavra não é o mesmo ou um subconjunto de um caractere de palavra, não há possibilidade de que o mecanismo de expressão regular cruze um limite de palavra ao corresponder caracteres de palavra. Portanto, para esta expressão regular, o backtracking nunca pode contribuir para o sucesso geral de qualquer partida. Ele só pode degradar o desempenho porque o mecanismo de expressão regular é forçado a salvar seu estado para cada correspondência preliminar bem-sucedida de um caractere de palavra.
Se você determinar que o backtracking não é necessário, poderá desativá-lo de algumas maneiras:
Ao definir a opção RegexOptions.NonBacktracking (introduzida no .NET 7). Para mais informações, consulte modo sem retrocesso.
Usando o elemento de linguagem
(?>subexpression)
, conhecido como grupo atómico. O exemplo a seguir analisa uma cadeia de caracteres de entrada usando duas expressões regulares. A primeira,\b\p{Lu}\w*\b
, baseia-se no retrocesso. O segundo,\b\p{Lu}(?>\w*)\b
, desativa o retrocesso. Como mostra a saída do exemplo, ambos produzem o mesmo resultado: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
Em muitos casos, o backtracking é essencial para fazer corresponder um padrão de expressão regular ao texto de entrada. No entanto, o backtracking excessivo pode degradar gravemente o desempenho e criar a impressão de que um aplicativo parou de responder. Em particular, esse problema surge quando os quantificadores são aninhados e o texto que corresponde à subexpressão externa é um subconjunto do texto que corresponde à subexpressão interna.
Advertência
Além de evitar o backtracking excessivo, você deve usar o recurso de tempo limite para garantir que o backtracking excessivo não degrade gravemente o desempenho da expressão regular. Para obter mais informações, consulte a seção Usar valores de tempo limite .
Por exemplo, o padrão ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$
de expressão regular destina-se a corresponder a um número de peça que consiste em pelo menos um caractere alfanumérico. Quaisquer caracteres adicionais podem consistir em um caractere alfanumérico, um hífen, um sublinhado ou um ponto, embora o último caractere deva ser alfanumérico. O símbolo do dólar termina o número da peça. Em alguns casos, esse padrão de expressão regular pode exibir um desempenho ruim porque os quantificadores estão aninhados e porque a subexpressão [0-9A-Z]
é um subconjunto da subexpressão [-.\w]*
.
Nesses casos, pode-se otimizar o desempenho da expressão regular removendo os quantificadores aninhados e substituindo a subexpressão externa por uma asserção de lookahead ou lookbehind de largura zero. As asserções lookahead e lookbehind são âncoras. Eles não movem o ponteiro na cadeia de caracteres de entrada, mas olham para frente ou para trás para verificar se uma condição especificada é atendida. Por exemplo, a expressão regular de número de peça pode ser reescrita como ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$
. Esse padrão de expressão regular é definido conforme mostrado na tabela a seguir:
Padrão | Descrição |
---|---|
^ |
Comece a correspondência no início da cadeia de caracteres de entrada. |
[0-9A-Z] |
Corresponder a um caractere alfanumérico. O número de peça deve consistir, pelo menos, neste carácter. |
[-.\w]* |
Corresponder a zero ou mais ocorrências de qualquer caractere de palavra, hífen ou ponto. |
\$ |
Corresponda a um cifrão. |
(?<=[0-9A-Z]) |
Olhe para trás do cifrão final para garantir que o caractere anterior é alfanumérico. |
$ |
Termine a partida no final da cadeia de caracteres de entrada. |
O exemplo a seguir ilustra o uso dessa expressão regular para corresponder a uma matriz que contém possíveis números de peça:
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.
A linguagem de expressão regular no .NET inclui os seguintes elementos de linguagem que você pode usar para eliminar quantificadores aninhados. Para obter mais informações, consulte Agrupando construções.
Elemento linguístico | Descrição |
---|---|
(?=
subexpression
)
|
Antevisão positiva de largura zero. Examina à frente da posição atual para determinar se subexpression corresponde à cadeia de caracteres de entrada. |
(?!
subexpression
)
|
Antevisão negativa de largura zero. Olha à frente da posição atual para determinar se subexpression não corresponde à cadeia de caracteres de entrada. |
(?<=
subexpression
)
|
Olhar positivo de largura zero. Examina a posição atual para determinar se subexpression corresponde à cadeia de caracteres de entrada. |
(?<!
subexpression
)
|
Olhar negativo de largura zero. Examina a posição anterior à atual para determinar se subexpression não corresponde à cadeia de caracteres de entrada. |
Usar valores de tempo limite
Se as suas expressões regulares processam entradas que quase correspondem ao padrão de expressão regular, muitas vezes podem depender de retrocessos excessivos, o que pode afetar o seu desempenho significativamente. Além de considerar cuidadosamente o uso de backtracking e testar a expressão regular em relação à entrada quase correspondente, você deve sempre definir um valor de tempo limite para minimizar o efeito do backtracking excessivo, se ocorrer.
O intervalo de tempo limite da expressão regular define o período durante o qual o motor de expressão regular procurará uma única correspondência antes de atingir o tempo limite. Dependendo do padrão da expressão regular e do texto de entrada, o tempo de execução pode exceder o intervalo de tempo limite especificado, mas não gastará mais tempo a retroceder do que o necessário. O intervalo de tempo limite padrão é Regex.InfiniteMatchTimeout, o que significa que a expressão regular não atingirá o tempo limite. Você pode substituir esse valor e definir um intervalo de tempo limite da seguinte maneira:
Chame o Regex(String, RegexOptions, TimeSpan) construtor para fornecer um valor de tempo limite quando você instanciar um Regex objeto.
Chame um método de correspondência de padrão estático, como Regex.Match(String, String, RegexOptions, TimeSpan) ou Regex.Replace(String, String, String, RegexOptions, TimeSpan), que inclua um
matchTimeout
parâmetro.Defina um valor em todo o processo ou em todo o domínio do aplicativo com um código como
AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromMilliseconds(100));
.
Se você definiu um intervalo de tempo limite e uma correspondência não é encontrada no final desse intervalo, o método de expressão regular lança uma RegexMatchTimeoutException exceção. No manipulador de exceções, você pode optar por repetir a correspondência com um intervalo de tempo limite maior, abandonar a tentativa de correspondência e assumir que não há correspondência, ou abandonar a tentativa de correspondência e registrar as informações de exceção para análise futura.
O exemplo a seguir define um GetWordData
método que instancia uma expressão regular com um intervalo de tempo limite de 350 milissegundos para calcular o número de palavras e o número médio de caracteres em uma palavra em um documento de texto. Se a operação correspondente atingir o tempo limite, o intervalo de tempo limite será aumentado em 350 milissegundos e o Regex objeto será reinstanciado. Se o novo intervalo de tempo limite exceder um segundo, o método relançará a exceção para o chamador.
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
Capturar apenas quando necessário
Expressões regulares no .NET suportam construções de agrupamento, que permitem agrupar um padrão de expressão regular em uma ou mais subexpressões. As construções de agrupamento mais usadas na linguagem de expressão regular .NET são (
subexpressão)
, que define um grupo de captura numerado, e (?<
subexpressão>
de nome)
, que define um grupo de captura nomeado. As construções de agrupamento são essenciais para criar backreferences e para definir uma subexpressão à qual um quantificador é aplicado.
No entanto, a utilização destes elementos linguísticos tem um custo. Eles fazem com que o GroupCollection objeto retornado pela propriedade Match.Groups seja preenchido com as capturas mais recentes, sem nome e nomeadas. Se uma única construção de agrupamento capturou várias substrings na string de entrada, eles também preenchem o objeto CaptureCollection retornado pela propriedade Group.Captures do grupo de captura específico com vários objetos Capture.
Muitas vezes, construções de agrupamento são usadas em uma expressão regular apenas para que quantificadores possam ser aplicados a elas. Os grupos capturados por essas subexpressões não são usados posteriormente. Por exemplo, a expressão \b(\w+[;,]?\s?)+[.?!]
regular é projetada para capturar uma frase inteira. A tabela a seguir descreve os elementos de linguagem neste padrão de expressão regular e seu efeito sobre as coleções Match do objeto Match.Groups e Group.Captures.
Padrão | Descrição |
---|---|
\b |
Comece a partida com um limite de palavras. |
\w+ |
Corresponde a um ou mais caracteres de palavras. |
[;,]? |
Corresponde a zero ou uma vírgula ou ponto-e-vírgula. |
\s? |
Corresponde a zero ou a um caractere de espaço em branco. |
(\w+[;,]?\s?)+ |
Corresponde a uma ou mais ocorrências de um ou mais caracteres de palavra seguidos por uma vírgula ou ponto-e-vírgula opcional seguida por um caractere de espaço em branco opcional. Este padrão define o primeiro grupo de captura, que é necessário para que a combinação de vários caracteres de palavras (ou seja, uma palavra) seguida por um símbolo de pontuação opcional seja repetida até que o mecanismo de expressão regular atinja o final de uma frase. |
[.?!] |
Corresponde a um ponto, ponto de interrogação ou ponto de exclamação. |
Como mostra o exemplo a seguir, quando uma correspondência é encontrada, tanto os objetos GroupCollection quanto CaptureCollection são preenchidos com capturas da correspondência. Neste caso, o grupo (\w+[;,]?\s?)
de captura existe para que o +
quantificador possa ser aplicado a ele, o que permite que o padrão de expressão regular corresponda a cada palavra em uma frase. Caso contrário, corresponderia à última palavra de uma frase.
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.
Quando você usa subexpressões apenas para aplicar quantificadores a elas e não está interessado no texto capturado, deve desativar as capturas de grupo. Por exemplo, o elemento de linguagem (?:subexpression)
impede o grupo ao qual é aplicado de capturar as subcadeias correspondentes. No exemplo a seguir, o padrão de expressão regular do exemplo anterior é alterado para \b(?:\w+[;,]?\s?)+[.?!]
. Como mostra a saída, ele impede que o mecanismo de expressão regular preencha as GroupCollection coleções e 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.
Você pode desativar as capturas de uma das seguintes maneiras:
Use o elemento de linguagem
(?:subexpression)
. Esse elemento impede a captura de substrings correspondentes no grupo ao qual se aplica. Ele não desabilita capturas de substring em nenhum grupo aninhado.Use a opção ExplicitCapture. Ele desativa todas as capturas sem nome ou implícitas no padrão de expressão regular. Quando o utilizador usa esta opção, somente as substrings que correspondem aos grupos nomeados definidos com o elemento de linguagem
(?<name>subexpression)
podem ser capturados. O ExplicitCapture sinalizador pode ser passado para ooptions
parâmetro de um Regex construtor de classe ou para ooptions
parâmetro de um Regex método de correspondência estática.Use a
n
opção no elemento(?imnsx)
linguagem. Esta opção desativa todas as capturas sem nome ou implícitas, a partir do ponto no padrão de expressão regular onde o elemento aparece. As capturas são desativadas até o final do padrão ou até que a opção habilite(-n)
capturas sem nome ou implícitas. Para obter mais informações, consulte Construções diversas.Use a
n
opção no elemento(?imnsx:subexpression)
linguagem. Esta opção desativa todas as capturas sem nome ou implícitas nosubexpression
. As captações feitas por grupos de captura aninhados sem nome ou implícitos também são desativadas.
Segurança de roscas
A Regex classe em si é segura para threads e imutável (apenas leitura). Ou seja, os objetos podem ser criados em qualquer thread e compartilhados entre threads, Regex
os métodos de correspondência podem ser chamados de qualquer thread e nunca alteram nenhum estado global.
No entanto, os objetos de resultado (Match
e MatchCollection
) retornados por Regex
devem ser usados em um único thread. Embora muitos desses objetos sejam logicamente imutáveis, suas implementações podem atrasar a computação de alguns resultados para melhorar o desempenho e, como resultado, os chamadores devem serializar o acesso a eles.
Se precisar compartilhar Regex
objetos de resultado em vários threads, esses objetos poderão ser convertidos em instâncias thread-safe ao chamar seus métodos sincronizados. Com exceção dos enumeradores, todas as classes de expressão regular são thread safe ou podem ser convertidas em objetos thread-safe por um método sincronizado.
Os recenseadores são a única exceção. Você deve serializar chamadas para enumeradores de coleção. A regra é que, se uma coleção puder ser enumerada em mais de um thread simultaneamente, você deverá sincronizar os métodos do enumerador no objeto raiz da coleção percorrida pelo enumerador.
Artigos relacionados
Título | Descrição |
---|---|
Detalhes do Comportamento das Expressões Regulares | Examina a implementação do mecanismo de expressão regular no .NET. O artigo se concentra na flexibilidade das expressões regulares e explica a responsabilidade do desenvolvedor em garantir a operação eficiente e robusta do mecanismo de expressão regular. |
Retrocesso | Explica o que é backtracking e como ele afeta o desempenho de expressões regulares e examina elementos de linguagem que fornecem alternativas ao backtracking. |
Linguagem de Expressão Regular - Referência Rápida | Descreve os elementos da linguagem de expressão regular no .NET e fornece links para documentação detalhada para cada elemento de linguagem. |