Menu

Regex en C#: Match, Replace, Split y grupos con ejemplos

Cómo usar expresiones regulares en C# con System.Text.RegularExpressions: IsMatch, Match y Matches, grupos numerados y con nombre, Replace con sustituciones y con una lambda, Split, RegexOptions, strings literales para los patrones, validar entradas, tiempos límite y el atributo GeneratedRegex de .NET 7.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Una expresión regular (regex) es un patrón que describe texto: "cuatro dígitos, un guion, dos dígitos", "una palabra que empieza por mayúscula", "cualquier cosa entre corchetes". En C#, la clase Regex de System.Text.RegularExpressions busca, extrae, sustituye y divide texto con esos patrones.

IsMatch, Match y Matches

Tres métodos cubren la mayoría de las búsquedas. IsMatch responde sí o no, Match devuelve la primera coincidencia y Matches las devuelve todas:

Salida:

True
2026-03-14 at index 22
4 matches:
  1042
  2026
  1043
  2026
Success: False, Value: []

Un Match fallido no devuelve null: devuelve un Match cuyo Success es false y cuyo Value está vacío. Comprueba siempre Success antes de usar una coincidencia.

La búsqueda de cuatro dígitos muestra cómo los patrones atrapan más de lo que se pretendía. \b\d{4}\b significa "exactamente cuatro dígitos como palabra completa", y los años de dentro de las fechas lo cumplen, porque - es un límite de palabra. Ajusta el patrón para que diga lo que quieres decir, por ejemplo (?<=Order |order )\d+, o usa grupos, a continuación.

Strings literales para los patrones

La sintaxis de regex usa barras invertidas por todas partes (\d, \s, \b), y también las usan los escapes de los strings de C#. En un string normal, "\d" ni siquiera compila (CS1009, secuencia de escape no reconocida) y "\b" compila como un carácter de retroceso, cambiando el patrón en silencio. Escribe los patrones como strings literales con @, donde una barra invertida es solo una barra invertida:

var a = new Regex("\\d+\\.\\d{2}");   // escaped twice: hard to read
var b = new Regex(@"\d+\.\d{2}");     // verbatim: what the regex engine sees

En un string literal, una comilla doble se escribe "". Los literales de string sin procesar de C# 11 ("""...""") evitan incluso eso.

Grupos: extraer partes de una coincidencia

Los paréntesis crean un grupo, y el texto de cada grupo está disponible después de la coincidencia. Los grupos se numeran desde 1 en el orden de su paréntesis de apertura; el grupo 0 es la coincidencia completa. Los grupos con nombre, (?<name>...), se leen mejor y sobreviven a los cambios del patrón:

Salida:

2026-03-14
ERROR
payments
Card declined for order 1042
60 by 90, area 5400

[ y ] tienen significado en regex (una clase de caracteres), así que para buscar los corchetes literales hace falta \[ y \]. Lo mismo ocurre con . * + ? ( ) { } ^ $ | \. Para buscar literalmente dentro de un patrón un string que aporta el usuario, pásalo por Regex.Escape, que escapa los metacaracteres por ti: Regex.Escape("price (USD)") devuelve price\ \(USD\) (los espacios también se escapan, lo que no hace daño).

Los paréntesis que solo agrupan, sin capturar, se escriben (?:...). Mantienen limpia la numeración de los grupos y son algo más rápidos.

Replace: sustituciones y lambdas

Regex.Replace sustituye todas las coincidencias. En el string de sustitución, $1 inserta el grupo 1, ${name} un grupo con nombre y $0 la coincidencia completa. Para todo lo que una plantilla no puede expresar, pasa una función (normalmente una lambda) que recibe cada Match y devuelve su sustitución:

Salida:

Invoiced 14/03/2026, paid 02/04/2026.
too many spaces
Card **** **** **** 1234
Tea 2.75, Scone 3.52, Jam 1.10

La máscara de la tarjeta usa una búsqueda hacia delante (lookahead), (?=...): coincide con un dígito solo si le siguen cuatro dígitos más, sin consumirlos. Las búsquedas alrededor ((?=...), (?!...), (?<=...), (?<!...)) comprueban el contexto sin incluirlo en la coincidencia, y eso es lo que permite que un solo Replace deje intactos los últimos cuatro dígitos.

Para texto plano sin patrón, string.Replace es más sencillo y más rápido; recurre a Regex.Replace cuando lo que hay que sustituir varía.

Split

Regex.Split divide en cada coincidencia de un patrón, lo que resuelve los separadores que varían:

Salida:

csharp / dotnet / regex / tutorial / beginner
Order Shipped Event Handler

Si el patrón contiene grupos de captura, Regex.Split incluye en el resultado los separadores capturados; usa (?:...) cuando no los quieras.

RegexOptions

Las opciones cambian cómo se comporta todo el patrón. Combínalas con |:

Salida:

1
2
error INFO Error
True

Las que usarás:

  • IgnoreCase: coincidencia sin distinguir mayúsculas (forma en línea (?i)).
  • Multiline: ^ y $ coinciden al principio y al final de cada línea, no solo de todo el string.
  • Singleline: . también coincide con \n (por defecto coincide con cualquier carácter salvo un salto de línea).
  • IgnorePatternWhitespace: los espacios del patrón se ignoran y # inicia un comentario, para patrones largos escritos en varias líneas.
  • CultureInvariant: con IgnoreCase, compara sin las reglas de la cultura actual.
  • Compiled: compila el patrón a IL una vez, para una regex que se usa muchas veces.

Validar entradas: anclas y la cuestión del email

Para validar, ancla el patrón con ^ y $; si no, pasa en cuanto una parte de la entrada coincide:

Salida:

PT-1000    loose=True  strict=True
pt-1000    loose=False strict=False
XPT-1000Y  loose=True  strict=False
PT-10      loose=False strict=False
ana@example.com   True
ana@example       False
ana @example.com  False
ana@@example.com  False

El patrón del email solo comprueba la forma: algo sin espacios ni @, una @ y un dominio con un punto. Es a propósito. La gramática real de las direcciones (RFC 5322) permite partes locales entre comillas, comentarios y dominios con IP literal; los patrones que intentan cubrirla llegan a cientos de caracteres y aun así rechazan direcciones válidas que la gente usa de verdad. Y una dirección sintácticamente perfecta puede rebotar. Comprueba la forma y después envía un email de confirmación. System.Net.Mail.MailAddress ofrece otra comprobación de forma si prefieres no escribir un patrón.

Rendimiento: métodos static, instancias, compilado

Los métodos static (Regex.IsMatch(input, pattern)) analizan el patrón y lo guardan en una pequeña caché (15 patrones por defecto), así que las llamadas repetidas con el mismo patrón son baratas. Para un patrón usado en un bucle crítico, crea una instancia de Regex y guárdala en un campo static readonly; añade RegexOptions.Compiled si se ejecuta miles de veces, a cambio de un arranque más lento y una coincidencia más rápida.

Tiempos límite y backtracking catastrófico

Algunos patrones tardan un tiempo exponencial con ciertas entradas. El clásico es un cuantificador anidado como ^(a+)+$ frente a "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!": el motor prueba todas las formas de repartir las a entre los dos + antes de rendirse. En un servidor web, una sola entrada así de un usuario puede tener ocupado un núcleo de la CPU (un ataque "ReDoS"). Da un tiempo límite a toda regex que maneje entradas no fiables:

var pattern = new Regex(@"^(\w+\s?)*$", RegexOptions.None, TimeSpan.FromMilliseconds(100));
try
{
    bool ok = pattern.IsMatch(userInput);
}
catch (RegexMatchTimeoutException)
{
    // treat as invalid input
}

Todos los métodos static tienen también una sobrecarga que recibe un tiempo límite. .NET 7 añadió además RegexOptions.NonBacktracking, un motor que garantiza un tiempo lineal a costa de algunas funciones (búsquedas alrededor, referencias hacia atrás).

GeneratedRegex (.NET 7)

.NET 7 añadió un generador de código fuente que convierte un patrón en código C# normal en tiempo de compilación. Obtienes la velocidad de Compiled sin coste de arranque, el código generado se puede leer en el IDE, y funciona con el trimming y con Native AOT:

public static partial class Patterns
{
    [GeneratedRegex(@"^[A-Z]{2}-\d{4}$", RegexOptions.IgnoreCase)]
    public static partial Regex ProductCode();
}

bool ok = Patterns.ProductCode().IsMatch("pt-1000");   // True

En .NET 7 y posteriores, es la forma recomendada para cualquier patrón conocido en tiempo de compilación.

Errores comunes

  • Faltan las anclas al validar. Sin ^...$, IsMatch acepta una entrada que solo contiene una coincidencia.
  • Strings normales para los patrones. "\b" es un retroceso, no un límite de palabra. Usa @"...".
  • Caracteres especiales sin escapar. . coincide con cualquier carácter; 3.50 como patrón también coincide con 3x50. Escapa con \., o con Regex.Escape para la entrada del usuario.
  • Cuantificadores codiciosos. <.*> sobre <b>bold</b> coincide con todo el string. Usa el perezoso .*? o una clase negada [^>]*.
  • Ningún tiempo límite con entradas no fiables. Un cuantificador anidado puede colgar una petición.
  • Regex para formatos estructurados. HTML, JSON y CSV con comillas necesitan un parser, no un patrón.

Preguntas frecuentes

¿Cómo uso regex en C#?

Añade using System.Text.RegularExpressions; y llama a los métodos static de Regex: Regex.IsMatch(input, pattern) devuelve un bool, Regex.Match devuelve la primera coincidencia, Regex.Matches todas, Regex.Replace sustituye y Regex.Split divide. Escribe los patrones como strings literales, @"\d+", para que las barras invertidas lleguen sin cambios al motor de regex.

¿Cómo obtengo el valor de un grupo de una coincidencia regex en C#?

Pon paréntesis alrededor de la parte que quieres y lee match.Groups[1].Value (los grupos se numeran desde 1; el grupo 0 es la coincidencia completa). Los grupos con nombre son más claros: (?<year>\d{4}) se lee con match.Groups["year"].Value. Comprueba antes match.Success, porque una coincidencia fallida tiene grupos vacíos en lugar de null.

¿Cómo sustituyo texto con regex en C#?

Regex.Replace(input, pattern, replacement) sustituye todas las coincidencias. La sustitución puede referirse a grupos: $1 para un grupo numerado, ${name} para uno con nombre y $0 para la coincidencia completa. Para una lógica que una plantilla no puede expresar, pasa una lambda: Regex.Replace(text, @"\d+", m => (int.Parse(m.Value) * 2).ToString()).

¿Cómo hago que una regex de C# no distinga mayúsculas?

Pasa RegexOptions.IgnoreCase: Regex.IsMatch(input, "error", RegexOptions.IgnoreCase). Las opciones se combinan con |, por ejemplo RegexOptions.IgnoreCase | RegexOptions.Multiline. También puedes activarla dentro del patrón con (?i).

¿Cómo valido una dirección de email con regex en C#?

Un patrón como ^[^@\s]+@[^@\s]+\.[^@\s]+$ detecta las erratas evidentes (falta la @, hay espacios, el dominio no tiene punto) y suele ser todo lo que necesita un formulario. Una regex no puede validar de verdad una dirección: la gramática completa permite formas que ningún patrón práctico maneja, y una dirección sintácticamente válida puede no existir. Comprueba la forma básica y después confirma enviando un email de verificación.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR