Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
O mecanismo de expressão regular no .NET é uma ferramenta avançada 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é mesmo dias.
Este artigo descreve algumas das práticas recomendadas que os desenvolvedores podem adotar para garantir que suas expressões regulares obtenham um desempenho ideal.
Aviso
Ao usar System.Text.RegularExpressions para processar entradas não confiáveis, passe um tempo limite. Um usuário mal-intencionado pode fornecer entrada a RegularExpressions
, causando um ataque de negação de serviço. APIs ASP.NET Core Framework que usam RegularExpressions
passam um tempo limite.
Considere a origem de entrada
Em geral, expressões regulares podem aceitar dois tipos de entrada: restritas ou ilimitadas. A entrada restrita é um texto que se origina de uma fonte conhecida ou confiável e segue um formato predefinido. A entrada não restrita é um texto que se origina de uma fonte não confiável, como um usuário da internet, e pode não seguir um formato predefinido ou esperado.
Os padrões de expressão regular geralmente são escritos para coincidir com dados válidos. Ou seja, os desenvolvedores examinam o texto que desejam corresponder e 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. Esse método torna um padrão de expressão regular adequado para corresponder uma entrada restrita. No entanto, não é adequado para corresponder à entrada irrestrita.
Para corresponder à entrada livre, uma expressão regular deve, com eficiência, lidar com três tipos de texto:
- 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 a entrada limitada. Se essa expressão regular também depender de retrocesso abrangente, o mecanismo de expressões regulares poderá gastar um período fora do normal (em alguns casos, muitas horas ou dias) processando texto aparentemente inócuo.
Aviso
O exemplo a seguir usa uma expressão regular propensa a retrocessos excessivos e que provavelmente rejeitará endereços de email válidos. Você não deve usá-lo em uma rotina de validação de email. Se você quiser uma expressão regular que valide endereços de email, consulte Como verificar se as cadeias de caracteres estão em 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 email. A expressão ^[0-9A-Z]([-.\w]*[0-9A-Z])*$
regular é escrita para processar o que é considerado um endereço de email válido. Um endereço de email válido consiste em um caractere alfanumérico, seguido por zero ou mais caracteres que podem ser alfanuméricos, períodos ou hifens. A expressão regular deve terminar com um caractere alfanumérico. No entanto, como mostra o exemplo a seguir, embora essa expressão regular trate facilmente a entrada válida, 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 email válido no mesmo intervalo de tempo, independentemente de seu comprimento. Por outro lado, quando o endereço de email quase válido tem mais de cinco caracteres, o tempo de processamento é aproximadamente duplo para cada caractere extra na cadeia de caracteres. 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 somente considerando o formato de entrada a ser correspondido, ela não leva em conta a entrada que não corresponde ao padrão. Isso, por sua vez, pode permitir que entradas irrestritas quase correspondentes ao padrão da expressão regular prejudiquem significativamente o desempenho.
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, especialmente se sua expressão regular for projetada para processar entradas irrestritas. Para obter mais informações, consulte a seção Tome conta do retrocesso.
Teste rigorosamente sua expressão regular usando entradas inválidas, quase válidas e válidas. Você pode usar Rex para gerar a entrada aleatoriamente para uma expressão regular específica. Rex é uma ferramenta de exploração de expressão regular da Microsoft Research.
Trate a instanciação de objetos 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 único fator maior que afeta o desempenho da expressão regular é a maneira como o Regex mecanismo é usado. Definir uma expressão regular envolve o acoplamento vigoroso do mecanismo de expressões regulares com um padrão de expressão regular. Esse processo de acoplamento é caro, quer envolva a instanciação de um Regex objeto passando ao 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 uma discussão detalhada sobre as implicações de desempenho de usar expressões regulares interpretadas e compiladas, veja a postagem no blog Otimizando o Desempenho de Expressões Regulares, Parte II: Assumindo o Controle do Retrocesso.
Você pode associar o mecanismo de expressão regular com 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 a 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ões de uma expressão regular interpretada em uma instância, que é o método padrão para associar o mecanismo de expressão regular ao padrão. Isso resulta quando um Regex objeto é instanciado sem um
options
argumento que inclua o Compiled sinalizador.Você pode criar uma instância de um objeto Regex e chamar um método instanciado de correspondência de padrões de uma expressão regular gerada pela origem. Essa 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 objeto Regex e chamar um método de correspondência de padrões de instância em uma expressão regular compilada. 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 pela qual você chama métodos de correspondência de expressões regulares pode afetar o desempenho do aplicativo. As seções a seguir discutem quando usar chamadas de método estático, expressões regulares geradas pela origem, 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 origem, 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
Métodos de expressão regular estáticos 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 é mostrado no código a seguir, no qual o evento Button de um controle Click é usado para chamar um método IsValidCurrency
, o qual verifica se o usuário inseriu um símbolo de moeda seguido por 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 reinstala um Regex objeto com o mesmo padrão. Isso, por sua vez, significa que o padrão de expressão regular deve ser recompilado sempre que o método for 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 criar uma instância de um objeto Regex sempre que você quiser chamar um método de correspondência de padrões e permite que o motor de expressões regulares recupere uma versão compilada a partir 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ático 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 decimais. O padrão é definido conforme 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. |
Expressões regulares interpretadas versus geradas pela origem versus compiladas
Padrões de expressão regular que não estão associados ao mecanismo de expressão regular por meio da especificação da opção Compiled 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ático é 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 em CIL para que o compilador JIT possa executá-los. Expressões regulares interpretadas reduzem o tempo de inicialização ao custo do 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 com o tempo de inicialização reduzido é superado pela velocidade de execução mais lenta.
Os padrões de expressão regular associados ao mecanismo de expressão regular por meio da especificação da opção Compiled 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 convertidos em CIL. Quando um método é chamado, o compilador JIT executa o CIL. Em contraste com expressões regulares interpretadas, 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 associados ao mecanismo de expressão regular por meio do adorno de um método de retorno Regex
com o atributo GeneratedRegexAttribute são gerados pela origem. O gerador de origem, que se conecta ao compilador, gera como código na linguagem C# uma implementação personalizada derivada de Regex
, com lógica semelhante à que RegexOptions.Compiled
emite no CIL. Você obtém todos os benefícios de desempenho de taxa de transferência de RegexOptions.Compiled
(mais, na verdade) e os benefícios de inicialização de Regex.CompileToAssembly
, mas sem a complexidade de CompileToAssembly
. A origem emitida faz parte do seu projeto, o que significa que ele também é facilmente acessível e depurável.
Para resumir, recomendamos que você:
- Use expressões regulares interpretadas quando você chama métodos de expressão regular com uma expressão regular específica com pouca frequência.
- Use expressões regulares geradas pela origem se você 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ê chama métodos de expressão regular com uma expressão regular específica com relativa frequência e está 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 em que os tempos de inicialização mais lentos de expressões regulares geradas pela origem ou compiladas 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 no texto da Carta Magna de William D. Guthrie e Outros Endereços. Como mostra a saída do exemplo, quando apenas 10 chamadas são feitas para métodos regulares de correspondência de expressões, uma expressão regular interpretada ou gerada pela origem 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 (nesse 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 |
Começar a correspondência em um limite de palavra. |
\w+ |
Corresponde a um ou mais caracteres de palavra. |
(\r?\n)|,?\s) |
Corresponde a um zero ou um retorno de carro seguido por um caractere de nova linha, ou zero ou uma vírgula seguida 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 por retornos de carro e por 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 palavra. |
[.?:;!] |
Corresponde a um ponto, um ponto de interrogação, dois-pontos, ponto e vírgula ou ponto de exclamação. |
Tome conta do retrocesso
Normalmente, o mecanismo de expressão regular usa a progressão linear para percorrer 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 abrir mão de uma parte das 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 retrocesso.
Dica
Para obter mais informações sobre o backtracking, consulte Detalhes do comportamento de expressão regular e backtracking. Para obter discussões detalhadas sobre retrocesso, consulte as Melhorias de Expressão Regular no .NET 7 e Otimizando o Desempenho de Expressões Regulares postagens no blog.
O suporte ao retrocesso proporciona poder e flexibilidade às expressões regulares. Ele também coloca a responsabilidade de controlar a operação do mecanismo de expressão regular nas mãos de desenvolvedores de expressões regulares. Como os desenvolvedores geralmente não estão cientes dessa responsabilidade, o uso indevido do retrocesso ou a confiança no retrocesso excessivo geralmente exerce o papel mais significativo na degradação do desempenho da expressão regular. Na pior das hipóteses, o tempo de execução pode dobrar para cada caractere adicional na cadeia de caracteres de entrada. Na verdade, com o uso de rastreamento inverso excessivo, é fácil criar o equivalente programático de um loop infinito se a entrada quase corresponder 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.
Normalmente, os aplicativos pagam uma multa de desempenho por usar o rastreamento inverso, mesmo não sendo essencial para uma correspondência. 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 |
Começar a correspondência em um limite de palavra. |
\p{Lu} |
Corresponde a um caractere minúsculo. |
\w* |
Corresponde a zero ou mais caracteres de palavra. |
\b |
Termina a correspondência em um limite de palavra. |
Como um delimitador de palavra não é o mesmo que, nem um subconjunto de, um caractere de palavra, não há nenhuma possibilidade de que o motor de expressões regulares cruze um delimitador de palavra ao encontrar caracteres de palavra. Portanto, para essa expressão regular, o rastreamento inverso nunca pode contribuir para o sucesso geral de qualquer correspondência. 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 retroceder não é necessário, você pode desabilitá-lo de algumas formas:
Definindo a opção RegexOptions.NonBacktracking (introduzida no .NET 7). Para obter mais informações, confira Modo sem retrocesso.
Usando o elemento de linguagem
(?>subexpression)
, conhecido como um 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
, depende do retrocesso. A segunda,\b\p{Lu}(?>\w*)\b
, desabilita 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 retrocesso é essencial para corresponder um padrão de expressão regular ao texto de entrada. No entanto, o retrocesso excessivo pode prejudicar severamente o desempenho e criar a impressão de que um aplicativo parou de responder. Em particular, este problema surge quando os quantificadores estão aninhados e o texto que corresponde à subexpressão externa é um subconjunto do texto que corresponde à subexpressão interna.
Aviso
Além de evitar o retrocesso excessivo, você deve usar o recurso de tempo limite para garantir que o retrocesso excessivo não degrade severamente 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 parte que consiste em pelo menos um caractere alfanumérico. Qualquer caractere adicional pode consistir em um caractere alfanumérico, um hífen, um sublinhado ou um período, embora o último caractere deve ser alfanumérico. Um cifrão 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, você pode otimizar o desempenho da expressão regular ao remover os quantificadores aninhados e substituir a subexpressão externa por uma declaração de lookahead ou lookbehind de largura zero. Asserções lookbehind e lookahead são âncoras. Eles não movem o ponteiro na cadeia de caracteres de entrada, mas, em vez disso, olham para frente ou para trás para verificar se uma condição especificada é atendida. Por exemplo, a expressão regular do número de parte 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 |
---|---|
^ |
Começar a correspondência no início da cadeia de caracteres de entrada. |
[0-9A-Z] |
Corresponder a um caractere alfanumérico. O número da parte deve consistir em pelo menos esse caractere. |
[-.\w]* |
Corresponde a zero ou mais ocorrências de qualquer caractere de palavra, hífen ou ponto. |
\$ |
Corresponde a um cifrão. |
(?<=[0-9A-Z]) |
Olhe atrás do sinal de dólar final para garantir que o caractere anterior seja alfanumérico. |
$ |
Finalizar a correspondência 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 parte:
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 Construções de agrupamento.
Elemento Language | Descrição |
---|---|
(?=
subexpression
)
|
Lookahead positivo de largura zero. Avalia à direita da posição atual para determinar se subexpression corresponde à cadeia de caracteres de entrada. |
(?!
subexpression
)
|
Lookahead negativo de largura zero. Avalia à direita da posição atual para determinar se subexpression não corresponde à cadeia de caracteres de entrada. |
(?<=
subexpression
)
|
Lookbehind positivo de largura zero. Avalia à esquerda da posição atual para determinar se subexpression corresponde à cadeia de caracteres de entrada. |
(?<!
subexpression
)
|
Lookbehind negativo de largura zero. Olha atrás da posição atual para determinar se subexpression não corresponde à cadeia de caracteres de entrada. |
Use valores de tempo limite
Se suas expressões regulares processarem entradas quase correspondentes ao padrão da expressão regular, elas poderão frequentemente confiar no retrocesso excessivo, o que afeta significativamente o desempenho. Além de considerar cuidadosamente o uso de rastreamento inverso e testar a expressão regular contra entradas quase correspondentes, você deve sempre definir um valor de tempo limite para garantir que o impacto do rastreamento inverso excessivo, caso ocorra, seja minimizado.
O intervalo de tempo limite de expressão regular define o período que o mecanismo de expressão regular procurará por uma única correspondência antes de atingir o tempo limite. Dependendo do padrão de expressão regular e do texto de entrada, o tempo de execução pode exceder o intervalo de tempo limite especificado, mas não passa mais tempo no rastreamento inverso do que o intervalo de tempo limite especificado. O intervalo de tempo limite padrão é Regex.InfiniteMatchTimeout, o que significa que a expressão regular não terá 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 ao instanciar um Regex objeto.
Chame um método de correspondência de padrões estáticos, 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 código como
AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromMilliseconds(100));
.
Se você tiver definido um intervalo de tempo limite e uma correspondência não for encontrada no final desse intervalo, o método de expressão regular gerará uma RegexMatchTimeoutException exceção. No manipulador de exceção, você pode optar por repetir a correspondência com um intervalo de tempo limite mais longo, 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 cria uma instância de 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 objeto Regex será reinstanciado. Se o novo intervalo de tempo limite exceder 1 segundo, o método gerará novamente a exceção no 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 somente quando necessário
Expressões regulares em constructos de agrupamento de suporte do .NET, 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 do .NET são (
a subexpressão)
, que define um grupo de captura numerada e (?<
a subexpressão>
de nome)
, que define um grupo de captura nomeado. Os constructos de agrupamento são essenciais para criar backreferences e para definir uma subexpressão à qual um quantificador é aplicado.
No entanto, o uso desses elementos de linguagem tem um custo. Eles fazem com que o objeto GroupCollection retornado pela propriedade Match.Groups seja preenchido com as capturas nomeadas ou sem nome mais recentes. Se um único constructo de agrupamento capturar várias substrings de caracteres na cadeia de caracteres de entrada, também preenchem o objeto CaptureCollection retornado pela propriedade Group.Captures de um grupo de captura específico com vários objetos Capture.
Muitas vezes, os constructos de agrupamento são usados em uma expressão regular apenas para que os quantificadores possam ser aplicados a eles. Os grupos capturados por essas subexpressões não são usados posteriormente. Por exemplo, a expressão \b(\w+[;,]?\s?)+[.?!]
regular foi 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 e Match.Groups do objeto Group.Captures.
Padrão | Descrição |
---|---|
\b |
Começar a correspondência em um limite de palavra. |
\w+ |
Corresponde a um ou mais caracteres de palavra. |
[;,]? |
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 opcional ou por ponto e vírgula seguido por um caractere de espaço em branco opcional. Esse padrão define o primeiro grupo de captura, que é necessário para que a combinação de vários caracteres de palavra (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 final, 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 os objetos CaptureCollection são preenchidos com capturas da correspondência. Nesse 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 em 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 eles e não está interessado no texto capturado, você deve desabilitar as capturas de grupo. Por exemplo, o (?:subexpression)
elemento de linguagem impede que o grupo ao qual ele se aplica capture subcadeias de caracteres correspondentes. No exemplo a seguir, o padrão de expressão regular do exemplo anterior é alterado para \b(?:\w+[;,]?\s?)+[.?!]
. Como a saída mostra, ele impede que o mecanismo de expressão regular preencha as coleções GroupCollection 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 desabilitar capturas de uma das seguintes maneiras:
Use o elemento de linguagem
(?:subexpression)
. Esse elemento impede a captura de subsequências correspondentes no grupo ao qual ele se aplica. Ele não desabilita capturas de substring de caracteres em grupos aninhados.Use a opção ExplicitCapture. Desabilita todas as capturas não nomeadas ou implícitas no padrão de expressão regular. Quando você usa essa opção, somente subcadeias de caracteres que correspondem a grupos nomeados definidos com o
(?<name>subexpression)
elemento de linguagem podem ser capturadas. 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 opção
n
no elemento de(?imnsx)
linguagem. Essa opção desabilita todas as capturas sem nome ou implícitas a partir do ponto no padrão de expressão regular em que o elemento aparece. As capturas são desabilitadas até o final do padrão ou até que a opção(-n)
habilite capturas sem nome ou implícitas. Para obter mais informações, consulte Construções Diversas.Use a opção
n
no elemento de(?imnsx:subexpression)
linguagem. Essa opção desabilita todas as capturas sem nome ou implícitas emsubexpression
. As capturas por grupos de capturas aninhadas sem nome ou implícitas também são desabilitadas.
Acesso thread-safe
A própria classe Regex é thread-safe e imutável (somente leitura). Ou seja, Regex
os objetos podem ser criados em qualquer thread e compartilhados entre threads; os métodos correspondentes podem ser chamados de qualquer thread e nunca alterar qualquer 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 você precisar compartilhar os objetos de resultado Regex
em vários threads, esses objetos poderão ser convertidos em instâncias thread-safe chamando 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 enumeradores são a única exceção. Você precisa serializar as chamadas a enumeradores de coleções. A regra é que, se uma coleção puder ser enumerada em mais de um thread simultaneamente, você deverá sincronizar métodos enumeradores no objeto raiz da coleção percorrida pelo enumerador.
Artigos relacionados
Título | Descrição |
---|---|
Detalhes do comportamento de 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 é o retrocesso e como ele afeta o desempenho da expressão regular e examina elementos de linguagem que fornecem alternativas ao retrocesso. |
Linguagem de expressões regulares – 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. |