Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
El motor de expresiones regulares de .NET es una herramienta eficaz y completa que procesa texto basado en coincidencias de patrones en lugar de comparar y buscar texto literal coincidente. En la mayoría de los casos, realiza la coincidencia de patrones de forma rápida y eficaz. Sin embargo, en algunos casos, el motor de expresiones regulares puede parecer lento. En casos extremos, incluso puede parecer dejar de responder a medida que procesa una entrada relativamente pequeña durante el transcurso de horas o incluso días.
En este artículo se describen algunos de los procedimientos recomendados que los desarrolladores pueden adoptar para garantizar que sus expresiones regulares logren un rendimiento óptimo.
Advertencia
Cuando se usa System.Text.RegularExpressions para procesar entradas que no son de confianza, pase un tiempo de expiración. Un usuario malintencionado puede proporcionar una entrada a RegularExpressions
, lo que provoca un ataque por denegación de servicio. Las API del marco ASP.NET Core en las que se usa RegularExpressions
pasan un tiempo de expiración.
Considere el origen de entrada.
En general, las expresiones regulares pueden aceptar dos tipos de entrada: restringidos o sin restricciones. La entrada restringida es un texto que se origina en un origen conocido o confiable y sigue un formato predefinido. La entrada sin restricciones es un texto que se origina en un origen no confiable, como un usuario web, y es posible que no siga un formato predefinido o esperado.
Los patrones de expresiones regulares a menudo se escriben para que coincidan con la entrada válida. Es decir, los desarrolladores examinan el texto que quieren que coincidan y, a continuación, escriben un patrón de expresión regular que coincida con él. A continuación, los desarrolladores determinan si este patrón requiere corrección o posterior elaboración mediante la prueba con varios elementos de entrada válidos. Cuando el patrón coincide con todas las entradas válidas supuestas, se declara que está lista para producción y se puede incluir en una aplicación publicada. Este enfoque hace que un patrón de expresión regular sea adecuado para la coincidencia de entradas restringidas. Sin embargo, no es adecuado para datos de entrada sin restricciones coincidentes.
Para hacer coincidir la entrada sin restricciones, una expresión regular debe controlar tres tipos de texto de forma eficaz:
- Texto que coincide con el patrón de expresión regular.
- Texto que no coincide con el patrón de expresión regular.
- Texto que casi coincide con el patrón de expresión regular.
El último tipo de texto es especialmente problemático para una expresión regular escrita para controlar la entrada restringida. Si esa expresión regular también se basa en un retroceso extenso, el motor de expresiones regulares puede pasar una cantidad de tiempo inordinar (en algunos casos, muchas horas o días) procesando texto aparentemente inocuo.
Advertencia
En el ejemplo siguiente se usa una expresión regular que es propensa a un retroceso excesivo y que es probable que rechace direcciones de correo electrónico válidas. No debe usarlo en una rutina de validación de correo electrónico. Si desea una expresión regular que valide las direcciones de correo electrónico, vea Cómo: Comprobar que las cadenas están en formato de correo electrónico válido.
Por ejemplo, considere una expresión regular común pero problemática para validar el alias de una dirección de correo electrónico. La expresión ^[0-9A-Z]([-.\w]*[0-9A-Z])*$
regular se escribe para procesar lo que se considera una dirección de correo electrónico válida. Una dirección de correo electrónico válida consta de un carácter alfanumérico, seguido de cero o más caracteres que pueden ser alfanuméricos, puntos o guiones. La expresión regular debe terminar con un carácter alfanumérico. Sin embargo, como se muestra en el ejemplo siguiente, aunque esta expresión regular controla fácilmente la entrada válida, su rendimiento es ineficaz cuando procesa una entrada casi válida:
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 se muestra en la salida del ejemplo anterior, el motor de expresiones regulares procesa el alias de correo electrónico válido en aproximadamente el mismo intervalo de tiempo, independientemente de su longitud. Por otro lado, cuando la dirección de correo electrónico casi válida tiene más de cinco caracteres, el tiempo de procesamiento se duplica aproximadamente para cada carácter adicional de la cadena. Por lo tanto, una cadena de 28 caracteres casi válida tardaría más de una hora en procesarse y una cadena de 33 caracteres casi válida tardaría casi un día en procesarse.
Dado que esta expresión regular se desarrolló únicamente teniendo en cuenta el formato de entrada que se va a hacer coincidir, no puede tener en cuenta la entrada que no coincide con el patrón. Este descuido, a su vez, puede permitir una entrada sin restricciones que se asemeja casi al patrón de expresión regular, deteriorando significativamente el rendimiento.
Para solucionar este problema, puede hacer lo siguiente:
Al desarrollar un patrón, debe tener en cuenta cómo el retroceso podría afectar al rendimiento del motor de expresiones regulares, especialmente si la expresión regular está diseñada para procesar entradas sin restricciones. Para obtener más información, consulte la sección Controlar el retroceso.
Pruebe exhaustivamente la expresión regular usando datos de entrada no válidos, casi válidos y válidos. Puede usar Rex para generar de forma aleatoria la entrada para una expresión regular determinada. Rex es una herramienta de exploración de expresiones regulares de Microsoft Research.
Controlar la creación de instancias de objeto correctamente
En el corazón del modelo de objeto de expresiones regulares de .NET está la clase System.Text.RegularExpressions.Regex, que representa el motor de expresiones regulares. A menudo, el factor más grande que afecta al rendimiento de expresiones regulares es la forma en que se usa el Regex motor. Definir una expresión regular implica acoplar estrechamente el motor de expresiones regulares con un patrón de expresión regular. Ese proceso de acoplamiento es costoso, ya sea que implique crear una instancia de un objeto Regex pasando a su constructor un patrón de expresión regular, o llamar a un método estático pasando el patrón de expresión regular y la cadena que se va a analizar.
Nota:
Para obtener una explicación detallada de las implicaciones de rendimiento del uso de expresiones regulares interpretadas y compiladas, consulte la entrada de blog Optimización del rendimiento de expresiones regulares, parte II: Tomar el control del retroceso.
Puede acoplar el motor de expresiones regulares con un patrón de expresión regular determinado y, a continuación, usar el motor para que coincida con el texto de varias maneras:
Puede llamar a un método estático de coincidencia de patrones, como Regex.Match(String, String). Este método no requiere la creación de instancias de un objeto de expresión regular.
Puede instanciar un objeto Regex y llamar a un método de coincidencia de patrones de una expresión regular interpretada, que es el método predeterminado para enlazar el motor de expresiones regulares a un patrón de expresión regular. Se produce cuando se crea una instancia de un objeto Regex sin un
options
argumento que incluye el indicador Compiled.Puede crear instancias de un objeto Regex y llamar a una instancia de un método de coincidencia de patrones de una expresión regular generada por código fuente. Esta técnica se recomienda en la mayoría de los casos. Para ello, coloque el GeneratedRegexAttribute atributo en un método parcial que devuelva
Regex
.Puede crear una instancia de un objeto Regex y llamar a un método de coincidencia de patrones para instancias de una expresión regular compilada. Los objetos de expresiones regulares representan patrones compilados cuando se instancia un Regex objeto con un
options
argumento que incluye la Compiled bandera.
La forma concreta en la que se llama a métodos de coincidencia de expresiones regulares puede afectar al rendimiento de la aplicación. En las secciones siguientes se describe cuándo usar llamadas a métodos estáticos, expresiones regulares generadas por el origen, expresiones regulares interpretadas y expresiones regulares compiladas para mejorar el rendimiento de la aplicación.
Importante
La forma de la llamada al método (estática, interpretada, generada por el origen y compilada) afecta al rendimiento si la misma expresión regular se usa repetidamente en llamadas a métodos o si una aplicación hace un uso extenso de objetos de expresión regular.
Expresiones regulares estáticas
Los métodos de expresiones regulares estáticas se recomiendan como alternativa a crear instancias repetidas de un objeto de expresión regular con la misma expresión regular. A diferencia de los patrones de expresiones regulares usados por los objetos de expresión regular, los códigos de operación (códigos de operación) o el lenguaje intermedio común compilado (CIL) de los patrones usados en las llamadas a métodos estáticos se almacenan en caché internamente por el motor de expresiones regulares.
Por ejemplo, un controlador de eventos llama con frecuencia a otro método para validar la entrada del usuario. Este ejemplo se refleja en el código siguiente, en el que se usa el evento de Button un Click control para llamar a un método denominado IsValidCurrency
, que comprueba si el usuario ha escrito un símbolo de moneda seguido de al menos un 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
En el ejemplo siguiente se muestra una implementación ineficaz del IsValidCurrency
método:
Nota:
Cada llamada al método reinstancia un objeto Regex con el mismo patrón. Esto, a su vez, significa que el patrón de expresión regular se debe volver a compilar cada vez que se llama al método .
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
Debe reemplazar el código ineficaz anterior por una llamada al método estático Regex.IsMatch(String, String) . Este enfoque elimina la necesidad de crear instancias de un Regex objeto cada vez que quiera llamar a un método de coincidencia de patrones y permite al motor de expresiones regulares recuperar una versión compilada de la expresión regular de su caché.
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
De forma predeterminada, los últimos 15 patrones de expresiones regulares estáticas usados se almacenan en caché. En el caso de las aplicaciones que requieren un mayor número de expresiones regulares estáticas almacenadas en caché, el tamaño de la memoria caché se puede ajustar estableciendo la Regex.CacheSize propiedad .
La expresión \p{Sc}+\s*\d+
regular que se usa en este ejemplo comprueba que la cadena de entrada tiene un símbolo de moneda y al menos un dígito decimal. El patrón se define como se muestra en la tabla siguiente:
Modelo | Descripción |
---|---|
\p{Sc}+ |
Coincide con uno o varios caracteres en la categoría Símbolo Unicode, Moneda. |
\s* |
Busca coincidencias con cero o más caracteres de espacio en blanco. |
\d+ |
Buscar coincidencias con uno o más dígitos decimales. |
Expresiones regulares interpretadas frente a generadas por código fuente frente a expresiones regulares compiladas
Los patrones de expresión regular que no están enlazados al motor de expresiones regulares a través de la especificación de la Compiled opción se interpretan. Cuando se crea una instancia de un objeto de expresión regular, el motor de expresiones regulares convierte la expresión regular en un conjunto de códigos de operación. Cuando se llama a un método de instancia, los códigos de operación se convierten en CIL y se ejecutan mediante el compilador JIT. Del mismo modo, cuando se llama a un método de expresión regular estática y no se encuentra la expresión regular en la memoria caché, el motor de expresiones regulares convierte la expresión regular en un conjunto de códigos de operación y los almacena en la memoria caché. A continuación, convierte estos códigos de operación en CIL para que el compilador JIT pueda ejecutarlos. Las expresiones regulares interpretadas reducen el tiempo de inicio a costa del tiempo de ejecución más lento. Debido a este proceso, se usan mejor cuando se usa la expresión regular en un pequeño número de llamadas a métodos, o si se desconoce el número exacto de llamadas a métodos de expresión regular, pero se espera que sea pequeño. A medida que aumenta el número de llamadas de método, la ganancia de rendimiento a partir del tiempo de inicio reducido se agota por la velocidad de ejecución más lenta.
Los patrones de expresión regular que están enlazados al motor de expresiones regulares mediante la especificación de la Compiled opción se compilan. Por lo tanto, cuando se crea una instancia de un objeto de expresión regular o cuando se llama a un método de expresión regular estática y no se encuentra la expresión regular en la memoria caché, el motor de expresiones regulares convierte la expresión regular en un conjunto intermedio de códigos de operación. Estos códigos se convierten entonces a CIL. Cuando se llama a un método, el compilador JIT ejecuta la CIL. A diferencia de las expresiones regulares interpretadas, las expresiones regulares compiladas aumentan el tiempo de inicio, pero ejecutan métodos individuales de coincidencia de patrones más rápidos. Como resultado, la ventaja de rendimiento que resulta de la compilación de la expresión regular aumenta en proporción al número de métodos de expresión regular llamados.
Los patrones de expresiones regulares que están enlazados al motor de expresiones regulares mediante el adorno de un método que devuelve Regex
con el atributo GeneratedRegexAttribute se generan por código fuente. El generador de origen, que se conecta al compilador, emite como código de C# una implementación derivada personalizada Regex
con lógica similar a la que RegexOptions.Compiled
emite en la CIL. Se obtienen todas las ventajas de rendimiento de RegexOptions.Compiled
(más, de hecho) y las ventajas de inicio de Regex.CompileToAssembly
, pero sin la complejidad de CompileToAssembly
. El código fuente que se emite forma parte del proyecto, lo que significa que también es fácil de ver y depurar.
Para resumir, le recomendamos que:
- Use expresiones regulares interpretadas al llamar a métodos de expresión regular con una expresión regular específica con poca frecuencia.
- Use expresiones regulares generadas a partir de la fuente si usa
Regex
en C# con argumentos conocidos en tiempo de compilación y utiliza una expresión regular específica con relativa frecuencia. - Use expresiones regulares compiladas cuando llame a métodos de expresión regular con una expresión regular específica con relativamente frecuencia y use .NET 6 o una versión anterior.
Es difícil determinar el umbral exacto en el que las velocidades de ejecución más lentas de las expresiones regulares interpretadas superan las ganancias de su tiempo de inicio reducido. También es difícil determinar el umbral en el que los tiempos de inicio más lentos de las expresiones regulares generadas por el origen o compiladas superan las ganancias de sus velocidades de ejecución más rápidas. Los umbrales dependen de varios factores, incluida la complejidad de la expresión regular y los datos específicos que procesa. Para determinar qué expresiones regulares ofrecen el mejor rendimiento para su escenario de aplicación concreto, puede usar la Stopwatch clase para comparar sus tiempos de ejecución.
En el ejemplo siguiente se compara el rendimiento de expresiones regulares compiladas, generadas a partir de código fuente e interpretadas al leer las primeras 10 oraciones y al leer todas las oraciones del texto de la obra de William D. Guthrie, Magna Carta, y otras conferencias. Como se muestra en la salida del ejemplo, cuando solo se realizan 10 llamadas a métodos de coincidencia de expresiones regulares, una expresión regular interpretada o generada por el origen ofrece un mejor rendimiento que una expresión regular compilada. Sin embargo, una expresión regular compilada ofrece un mejor rendimiento cuando se realiza un gran número de llamadas (en este caso, más de 13 000).
const string Pattern = @"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]";
static readonly HttpClient s_client = new();
[GeneratedRegex(Pattern, RegexOptions.Singleline)]
private static partial Regex GeneratedRegex();
public async static Task RunIt()
{
Stopwatch sw;
Match match;
int ctr;
string text =
await s_client.GetStringAsync("https://www.gutenberg.org/cache/epub/64197/pg64197.txt");
// Read first ten sentences with interpreted regex.
Console.WriteLine("10 Sentences with Interpreted Regex:");
sw = Stopwatch.StartNew();
Regex int10 = new(Pattern, RegexOptions.Singleline);
match = int10.Match(text);
for (ctr = 0; ctr <= 9; ctr++)
{
if (match.Success)
// Do nothing with the match except get the next match.
match = match.NextMatch();
else
break;
}
sw.Stop();
Console.WriteLine($" {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
*/
El patrón de expresión regular usado en el ejemplo, \b(\w+((\r?\n)|,?\s))*\w+[.?:;!]
, se define como se muestra en la tabla siguiente:
Modelo | Descripción |
---|---|
\b |
Iniciar la búsqueda de coincidencias en un límite de palabras. |
\w+ |
Busca coincidencias con uno o más caracteres alfabéticos. |
(\r?\n)|,?\s) |
Busca coincidencias con cero o un retorno de carro seguido de un carácter de nueva línea, o cero o una coma seguida de un carácter de espacio en blanco. |
(\w+((\r?\n)|,?\s))* |
Busca coincidencias con cero o más apariciones de uno o más caracteres alfabéticos que van seguidos de cero o un retorno de carro y un carácter de nueva línea, o de cero o una coma seguida de un carácter de espacio en blanco. |
\w+ |
Busca coincidencias con uno o más caracteres alfabéticos. |
[.?:;!] |
Busca coincidencias con un punto, un signo de interrogación, dos puntos, punto y coma o un signo de exclamación. |
Controlar el retroceso
Normalmente, el motor de expresiones regulares usa la progresión lineal para desplazarse por una cadena de entrada y compararla con un patrón de expresión regular. Sin embargo, cuando se usan cuantificadores indeterminados como *
, +
y ?
en un patrón de expresión regular, el motor de expresiones regulares podría renunciar a una parte de coincidencias parciales correctas y volver a un estado guardado previamente para buscar una coincidencia correcta para todo el patrón. Este proceso se conoce como retroceso.
Sugerencia
Para obtener más información sobre el retroceso, vea Detalles del comportamiento de expresiones regulares y Retroceso. Para obtener discusiones detalladas sobre el retroceso, consulte las entradas del blog Mejoras de expresiones regulares en .NET 7 y Optimización del rendimiento de expresiones regulares .
La compatibilidad con el retroceso aporta a las expresiones regulares eficacia y flexibilidad. También pone la responsabilidad de controlar el funcionamiento del motor de expresiones regulares en manos de los desarrolladores de expresiones regulares. Puesto que los desarrolladores no suelen ser conscientes de esta responsabilidad, su uso incorrecto del retroceso o su dependencia de un retroceso excesivo suele desempeñar el rol más significativo en la degradación del rendimiento de las expresiones regulares. En el peor de los casos, el tiempo de ejecución puede duplicarse para cada carácter adicional de la cadena de entrada. De hecho, si se utiliza la vuelta atrás (backtracking) en exceso, es fácil crear el equivalente programático de un bucle sin fin si la entrada casi coincide con el patrón de la expresión regular. El motor de expresiones regulares puede tardar horas o incluso días en procesar una cadena de entrada relativamente corta.
A menudo, las aplicaciones sufren una reducción del rendimiento por usar la vuelta atrás (backtracking), a pesar de que esta no es esencial para una coincidencia. Por ejemplo, la expresión \b\p{Lu}\w*\b
regular coincide con todas las palabras que comienzan con un carácter en mayúsculas, como se muestra en la tabla siguiente:
Modelo | Descripción |
---|---|
\b |
Iniciar la búsqueda de coincidencias en un límite de palabras. |
\p{Lu} |
Busca una coincidencia con un carácter en mayúsculas. |
\w* |
Busca una coincidencia con cero o más caracteres alfabéticos. |
\b |
Finalizar la búsqueda de coincidencias en un límite de palabras. |
Dado que un límite de palabras no es lo mismo que, ni un subconjunto de, un carácter de palabra, no existe la posibilidad de que el motor de expresiones regulares cruce un límite de palabras cuando trate de coincidir con caracteres de palabra. Por lo tanto, para esta expresión regular, la vuelta atrás (backtracking) nunca puede contribuir al éxito general de ninguna coincidencia. Solo puede degradar el rendimiento porque el motor de expresiones regulares se ve obligado a guardar su estado para cada coincidencia preliminar correcta de un carácter alfabético.
Si determina que el retroceso no es necesario, puede deshabilitarlo de dos maneras:
Al establecer la RegexOptions.NonBacktracking opción (introducida en .NET 7). Para obtener más información, consulte modo sin retroceso.
Mediante el uso del
(?>subexpression)
elemento de lenguaje, conocido como un grupo atómico. En el ejemplo siguiente se analiza una cadena de entrada mediante dos expresiones regulares. La primera,\b\p{Lu}\w*\b
, se basa en el retroceso. La segunda,\b\p{Lu}(?>\w*)\b
, deshabilita el retroceso. Como se muestra en la salida del ejemplo, ambos generan el mismo 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
En muchos casos, el retroceso es esencial para hacer coincidir un patrón de expresión regular con el texto de entrada. Sin embargo, el retroceso excesivo puede degradar gravemente el rendimiento y crear la impresión de que una aplicación ha dejado de responder. En concreto, este problema surge cuando los cuantificadores están anidados y el texto que coincide con la subexpresión externa es un subconjunto del texto que coincide con la subexpresión interna.
Advertencia
Además de evitar un retroceso excesivo, debe usar la característica de tiempo de espera para asegurarse de que el retroceso excesivo no degrada gravemente el rendimiento de las expresiones regulares. Para obtener más información, consulte la sección Uso de valores de tiempo de espera .
Por ejemplo, el patrón de expresión regular ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$
está diseñado para buscar coincidencias con un número de pieza que contiene al menos un carácter alfanumérico. Cualquier carácter adicional puede constar de un carácter alfanumérico, un guión, un carácter de subrayado o un punto, aunque el último carácter debe ser alfanumérico. Un signo de dólar indica el final del número de pieza. En algunos casos, este patrón de expresión regular puede mostrar un rendimiento deficiente porque los cuantificadores están anidados y porque la subexpresión [0-9A-Z]
es un subconjunto de la subexpresión [-.\w]*
.
En estos casos, puede optimizar el rendimiento de la expresión regular quitando los cuantificadores anidados y reemplazando la subexpresión externa con una aserción de búsqueda anticipada o de búsqueda tardía de ancho cero. Las aserciones de búsqueda anticipada y de búsqueda tardía son delimitadores. No mueven el puntero en la cadena de entrada, sino que miran hacia delante o detrás para comprobar si se cumple una condición especificada. Por ejemplo, la expresión regular de número de pieza se puede volver a escribir como ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$
. Este patrón de expresión regular se define como se muestra en la tabla siguiente:
Modelo | Descripción |
---|---|
^ |
Iniciar la búsqueda de coincidencias con el principio de la cadena de entrada. |
[0-9A-Z] |
Encuentra un carácter alfanumérico. El número de pieza debe constar al menos de este carácter. |
[-.\w]* |
Buscar coincidencias con cero o más apariciones de cualquier carácter alfabético, guión o punto. |
\$ |
Buscar coincidencias con un signo de dólar. |
(?<=[0-9A-Z]) |
Mire detrás del signo final del dólar para asegurarse de que el carácter anterior es alfanumérico. |
$ |
Finalizar la búsqueda de coincidencias al final de la cadena de entrada. |
En el ejemplo siguiente se muestra el uso de esta expresión regular para que coincida con una matriz que contenga posibles 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.
El lenguaje de expresiones regulares de .NET incluye los siguientes elementos de lenguaje que puede usar para eliminar cuantificadores anidados. Para obtener más información, vea Construcciones de agrupación.
Elemento Language | Descripción |
---|---|
(?=
subexpression
)
|
Búsqueda anticipada positiva de ancho cero. Busca con anticipación la posición actual para determinar si subexpression coincide con la cadena de entrada. |
(?!
subexpression
)
|
Búsqueda anticipada negativa de ancho cero. Examina con antelación la posición actual para determinar si subexpression no coincide con la cadena de entrada. |
(?<=
subexpression
)
|
Búsqueda tardía positiva de ancho cero. Examina la posición previa para determinar si subexpression coincide con la cadena de entrada. |
(?<!
subexpression
)
|
Búsqueda tardía negativa de ancho cero. Realiza una búsqueda tardía de la posición actual para determinar si subexpression no coincide con la cadena de entrada. |
Utilice valores de tiempo de espera
Si sus expresiones regulares procesan datos de entrada que prácticamente coinciden con el patrón de expresiones regulares, normalmente se puede usar el retroceso excesivo, que afecta enormemente al rendimiento. Además de analizar cuidadosamente el uso de la vuelta atrás (backtracking) y probar la expresión regular en entradas casi coincidentes, debe establecer siempre un valor de tiempo de espera para minimizar el efecto de la excesiva vuelta atrás (backtracking), en caso de que se produzca.
El intervalo de tiempo de espera para expresiones regulares define el período durante el cual el motor de búsqueda de expresiones regulares intentará encontrar una coincidencia antes de que se agote el tiempo de espera. Dependiendo del patrón de la expresión regular y el texto de entrada, el tiempo de ejecución podría exceder el intervalo de tiempo de espera especificado, pero no dedicará más tiempo al retroceso que el intervalo de tiempo de espera especificado. El intervalo de tiempo de espera predeterminado es Regex.InfiniteMatchTimeout, lo que significa que la expresión regular no agotará el tiempo de espera. Puede invalidar este valor y definir un intervalo de tiempo de espera como se indica a continuación:
Llame al Regex(String, RegexOptions, TimeSpan) constructor para proporcionar un valor de tiempo de espera al crear una instancia de un Regex objeto.
Llame a un método de coincidencia de patrones estáticos, como Regex.Match(String, String, RegexOptions, TimeSpan) o Regex.Replace(String, String, String, RegexOptions, TimeSpan), que incluya un
matchTimeout
parámetro .Establezca un valor en todo el proceso o en todo el dominio de la aplicación con código como
AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromMilliseconds(100));
.
Si ha definido un intervalo de tiempo de espera y no se encuentra una coincidencia al final de ese intervalo, el método de expresión regular produce una RegexMatchTimeoutException excepción. En el controlador de excepciones, puede optar por volver a intentar la coincidencia con un intervalo de tiempo de espera más largo, abandonar el intento de coincidencia y suponer que no hay ninguna coincidencia, o abandonar el intento de coincidencia y registrar la información de excepción para el análisis futuro.
En el ejemplo siguiente se define un GetWordData
método que crea una instancia de una expresión regular con un intervalo de tiempo de espera de 350 milisegundos para calcular el número de palabras y el número medio de caracteres de una palabra de un documento de texto. Si se agota el tiempo de espera en la operación de coincidencia, el intervalo de tiempo de espera se incrementa en 350 milisegundos y el objeto Regex se reinicializa. Si el nuevo intervalo de tiempo de espera es mayor de 1 segundo, el método vuelve a producir la excepción y se la envía al autor de la llamada.
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 solo cuando sea necesario
Las expresiones regulares de .NET admiten construcciones de agrupación, que permiten agrupar un patrón de expresión regular en una o varias subexpresiones. Las construcciones de agrupación más usadas en el lenguaje de expresiones regulares de .NET son (
subexpresión, que define un grupo de captura numerado y )
(?<
>
de nombres)
, que define un grupo de captura con nombre. Las construcciones de agrupación son esenciales para crear referencias inversas y para definir una subexpresión a la que se aplica un cuantificador.
Sin embargo, el uso de estos elementos de lenguaje tiene un costo. Hacen que el objeto GroupCollection devuelto por la propiedad Match.Groups se rellene con las capturas con nombre o sin nombre más recientes. Si una única construcción de agrupación ha capturado varias subcadenas en la cadena de entrada, también rellenan el objeto CaptureCollection devuelto por la propiedad Group.Captures de un grupo de captura determinado con varios objetos Capture.
A menudo, las construcciones de agrupación se usan en una expresión regular solo para que los cuantificadores se puedan aplicar a ellos. Los grupos capturados por estas subexpresiones no se usan más adelante. Por ejemplo, la expresión \b(\w+[;,]?\s?)+[.?!]
regular está diseñada para capturar una oración completa. En la tabla siguiente se describen los elementos de lenguaje de este patrón de expresión regular y su efecto en las colecciones Match y Match.Groups del objeto Group.Captures.
Modelo | Descripción |
---|---|
\b |
Iniciar la búsqueda de coincidencias en un límite de palabras. |
\w+ |
Busca coincidencias con uno o más caracteres alfabéticos. |
[;,]? |
Busca coincidencias con cero o una coma o un punto y coma. |
\s? |
Busca coincidencias con cero o un carácter de espacio en blanco. |
(\w+[;,]?\s?)+ |
Busca coincidencias con una o más apariciones de uno o más caracteres alfabéticos seguidos de una coma o un punto y coma opcional, seguido de un carácter opcional de espacio en blanco. Este patrón define el primer grupo de captura, que es necesario para que la combinación de varios caracteres de palabras (es decir, una palabra) seguida de un símbolo de puntuación opcional se repetirá hasta que el motor de expresiones regulares llegue al final de una oración. |
[.?!] |
Busca coincidencias con un punto, un signo de interrogación o un signo de exclamación. |
Como se muestra en el ejemplo siguiente, cuando se encuentra una coincidencia, los objetos GroupCollection y CaptureCollection se rellenan con capturas de la coincidencia. En este caso, el grupo (\w+[;,]?\s?)
de captura existe para que el +
cuantificador se pueda aplicar a él, lo que permite que el patrón de expresión regular coincida con cada palabra de una oración. De lo contrario, coincidiría con la última palabra de una 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.
Cuando usa subexpresiones solo para aplicar cuantificadores a ellos y no está interesado en el texto capturado, debe deshabilitar las capturas de grupo. Por ejemplo, el (?:subexpression)
elemento de lenguaje impide que el grupo al que se aplique capture subcadenas coincidentes. En el ejemplo siguiente, el patrón de expresión regular del ejemplo anterior se cambia a \b(?:\w+[;,]?\s?)+[.?!]
. Como se muestra en la salida, impide que el motor de expresiones regulares rellene las colecciones GroupCollection y 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.
Puede deshabilitar las capturas de una de las maneras siguientes:
Utilice el
(?:subexpression)
elemento de idioma. Este elemento impide la captura de subcadenas coincidentes en el grupo al que se aplica. No deshabilita las capturas de subcadenas en ningún grupo anidado.Use la opción ExplicitCapture. Inactiva todas las capturas anónimas o implícitas en el patrón de expresión regular. Cuando se usa esta opción, solo se pueden capturar subcadenas que coincidan con grupos con nombre definidos con el
(?<name>subexpression)
elemento de lenguaje. La ExplicitCapture marca se puede pasar aloptions
parámetro de un Regex constructor de clase o aloptions
parámetro de un Regex método de coincidencia estático.Usa la opción
n
en el elemento de idioma(?imnsx)
. Esta opción deshabilita todas las capturas sin nombre o implícitas desde el punto del patrón de expresión regular en el que aparece el elemento. Las capturas se deshabilitan hasta el final del patrón o hasta que la(-n)
opción habilita capturas sin nombre o implícitas. Para obtener más información, vea Construcciones varias.Usa la opción
n
en el elemento de idioma(?imnsx:subexpression)
. Esta opción deshabilita todas las capturas sin nombre o implícitas ensubexpression
. Las capturas por grupos de captura anidados sin nombre o implícitos también se deshabilitan.
Seguridad de hilos
La clase Regex es en sí misma segura para subprocesos e inmutable (de solo lectura). Es decir, Regex
los objetos se pueden crear en cualquier subproceso y compartirse entre subprocesos; se puede llamar a métodos coincidentes desde cualquier subproceso y nunca modificar ningún estado global.
Sin embargo, los objetos de resultado (Match
y MatchCollection
) devueltos por Regex
deben usarse en un único subproceso. Aunque muchos de estos objetos son inmutables lógicamente, sus implementaciones podrían retrasar el cálculo de algunos resultados para mejorar el rendimiento y, como resultado, los autores de llamadas deben serializar el acceso a ellos.
Si es necesario compartir objetos de resultado de Regex
en varios subprocesos, estos objetos se pueden convertir en instancias seguras para subprocesos llamando a sus métodos sincronizados. Con la excepción de los enumeradores, todas las clases de expresiones regulares son seguras para subprocesos o se pueden convertir en objetos seguros para subprocesos mediante un método sincronizado.
Los enumeradores son la única excepción. Debe serializar las llamadas a enumeradores de colecciones. La regla es que si una colección se puede enumerar simultáneamente en más de un subproceso, debería sincronizar los métodos del enumerador en el objeto raíz de la colección que este recorre.
Artículos relacionados
Título | Descripción |
---|---|
Detalles del comportamiento de expresiones regulares | Examina la implementación del motor de expresiones regulares en .NET. El artículo se centra en la flexibilidad de las expresiones regulares y explica la responsabilidad del desarrollador para garantizar el funcionamiento eficaz y sólido del motor de expresiones regulares. |
Retroceso | Explica qué es el retroceso y cómo afecta al rendimiento de expresiones regulares y examina los elementos del lenguaje que proporcionan alternativas al retroceso. |
Lenguaje de expresiones regulares: referencia rápida | Describe los elementos del lenguaje de expresiones regulares en .NET y proporciona vínculos a documentación detallada para cada elemento de lenguaje. |