Uma expressão regular (regex) é um padrão que descreve texto: "quatro dígitos, um hífen, dois dígitos", "uma palavra que começa com letra maiúscula", "qualquer coisa entre colchetes". Em C#, a classe Regex, em System.Text.RegularExpressions, encontra, extrai, substitui e divide texto com esses padrões.
IsMatch, Match e Matches
Três métodos cobrem a maioria das buscas. IsMatch responde sim ou não, Match retorna a primeira correspondência e Matches retorna todas elas:
Saída:
True
2026-03-14 at index 22
4 matches:
1042
2026
1043
2026
Success: False, Value: []
Um Match que falhou não retorna null: retorna um Match cujo Success é false e cujo Value é vazio. Sempre teste o Success antes de usar uma correspondência.
A busca por quatro dígitos mostra como os padrões pegam mais do que se pretendia. \b\d{4}\b significa "exatamente quatro dígitos como uma palavra inteira", e os anos dentro das datas se encaixam, porque - é um limite de palavra. Aperte o padrão para dizer o que você quer, por exemplo (?<=Order |order )\d+, ou use grupos, a seguir.
Strings verbatim para padrões
A sintaxe de regex usa barras invertidas em todo lugar (\d, \s, \b), e os escapes de string de C# também. Em uma string comum, "\d" nem compila (CS1009, unrecognized escape sequence) e "\b" compila como um caractere de backspace, mudando o padrão sem aviso. Escreva os padrões como strings verbatim com @, em que uma barra invertida é só uma 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
Em uma string verbatim, uma aspa dupla se escreve "". Os raw string literals do C# 11 ("""...""") evitam até isso.
Grupos: extraindo partes de uma correspondência
Parênteses criam um grupo, e o texto de cada grupo fica disponível depois da correspondência. Os grupos são numerados a partir de 1, na ordem dos parênteses de abertura; o grupo 0 é a correspondência inteira. Grupos nomeados, (?<name>...), são mais fáceis de ler e sobrevivem a edições no padrão:
Saída:
2026-03-14
ERROR
payments
Card declined for order 1042
60 by 90, area 5400
[ e ] têm um significado em regex (uma classe de caracteres), então para corresponder aos colchetes literais é preciso \[ e \]. O mesmo vale para . * + ? ( ) { } ^ $ | \. Para corresponder literalmente a uma string fornecida pelo usuário dentro de um padrão, passe-a por Regex.Escape, que escapa os metacaracteres para você: Regex.Escape("price (USD)") retorna price\ \(USD\) (os espaços também são escapados, o que não causa problema).
Parênteses que só agrupam, sem capturar, se escrevem (?:...). Eles mantêm a numeração dos grupos limpa e são um pouco mais rápidos.
Replace: substituições e lambdas
Regex.Replace substitui todas as correspondências. Na string de substituição, $1 insere o grupo 1, ${name} um grupo nomeado e $0 a correspondência inteira. Para qualquer coisa que um modelo não consiga expressar, passe uma função (normalmente uma lambda) que recebe cada Match e retorna a sua substituição:
Saída:
Invoiced 14/03/2026, paid 02/04/2026.
too many spaces
Card **** **** **** 1234
Tea 2.75, Scone 3.52, Jam 1.10
A máscara do cartão usa um lookahead, (?=...): ele corresponde a um dígito só se mais quatro dígitos vierem depois, sem consumi-los. Os lookarounds ((?=...), (?!...), (?<=...), (?<!...)) testam o contexto sem incluí-lo na correspondência, e é isso que permite a um único Replace manter intactos os últimos quatro dígitos.
Para texto simples, sem padrão, string.Replace é mais simples e mais rápido; recorra a Regex.Replace quando o que precisa ser substituído varia.
Split
Regex.Split divide a cada correspondência de um padrão, o que resolve separadores que variam:
Saída:
csharp / dotnet / regex / tutorial / beginner
Order Shipped Event Handler
Se o padrão contiver grupos de captura, Regex.Split inclui os separadores capturados no resultado; use (?:...) quando não quiser isso.
RegexOptions
As opções mudam o comportamento do padrão inteiro. Combine-as com |:
Saída:
1
2
error INFO Error
True
As que você vai usar:
IgnoreCase: correspondência sem diferenciar maiúsculas e minúsculas (forma inline(?i)).Multiline:^e$correspondem ao início e ao fim de cada linha, não só da string inteira.Singleline:.também corresponde a\n(por padrão ele corresponde a qualquer caractere, exceto uma quebra de linha).IgnorePatternWhitespace: os espaços no padrão são ignorados e#inicia um comentário, para padrões longos escritos em várias linhas.CultureInvariant: comIgnoreCase, compara sem as regras da cultura atual.Compiled: compila o padrão para IL uma vez, para uma regex usada muitas vezes.
Validando entradas: âncoras e a questão do e-mail
Para validação, ancore o padrão com ^ e $, senão ele passa assim que parte da entrada corresponde:
Saída:
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
O padrão de e-mail só verifica a forma: algo sem espaços nem @, um @, um domínio com um ponto. Isso é de propósito. A gramática real dos endereços (RFC 5322) permite partes locais entre aspas, comentários e domínios com IP literal; padrões que tentam cobri-la chegam a centenas de caracteres e ainda rejeitam endereços válidos que as pessoas usam de verdade. E um endereço sintaticamente perfeito ainda pode voltar. Verifique a forma e depois envie um e-mail de confirmação. System.Net.Mail.MailAddress oferece outra verificação de forma, se você preferir não escrever um padrão.
Desempenho: métodos static, instâncias, compiled
Os métodos static (Regex.IsMatch(input, pattern)) interpretam o padrão e o guardam em um pequeno cache (15 padrões por padrão), então chamadas repetidas com o mesmo padrão são baratas. Para um padrão usado em um laço crítico, crie uma instância de Regex e guarde-a em um campo static readonly; acrescente RegexOptions.Compiled se ele executar milhares de vezes, trocando uma inicialização mais lenta por correspondências mais rápidas.
Timeouts e backtracking catastrófico
Alguns padrões levam tempo exponencial em certas entradas. O clássico é um quantificador aninhado como ^(a+)+$ contra "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!": o motor tenta todas as formas de dividir os a entre os dois + antes de desistir. Em um servidor web, uma única entrada dessas vinda de um usuário pode travar um núcleo da CPU (um ataque "ReDoS"). Dê um timeout a toda regex que trata entradas não confiáveis:
var pattern = new Regex(@"^(\w+\s?)*$", RegexOptions.None, TimeSpan.FromMilliseconds(100));
try
{
bool ok = pattern.IsMatch(userInput);
}
catch (RegexMatchTimeoutException)
{
// treat as invalid input
}
Todo método static também tem uma sobrecarga que recebe um timeout. O .NET 7 também adicionou RegexOptions.NonBacktracking, um motor que garante tempo linear ao custo de alguns recursos (lookarounds, retrorreferências).
GeneratedRegex (.NET 7)
O .NET 7 adicionou um source generator que transforma um padrão em código C# comum em tempo de compilação. Você ganha a velocidade do Compiled sem custo de inicialização, o código gerado pode ser lido na IDE e funciona com trimming e 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
No .NET 7 em diante, essa é a forma recomendada para qualquer padrão conhecido em tempo de compilação.
Erros comuns
- Faltar âncoras na validação. Sem
^...$,IsMatchaceita entradas que só contêm uma correspondência. - Strings comuns para os padrões.
"\b"é um backspace, não um limite de palavra. Use@"...". - Caracteres especiais sem escape.
.corresponde a qualquer caractere;3.50como padrão também corresponde a3x50. Escape com\., ou comRegex.Escapepara entradas do usuário. - Quantificadores gananciosos.
<.*>em<b>bold</b>corresponde à string inteira. Use o preguiçoso.*?ou uma classe negada[^>]*. - Nenhum timeout em entradas não confiáveis. Um quantificador aninhado pode travar uma requisição.
- Regex para formatos estruturados. HTML, JSON e CSV com aspas precisam de um parser, não de um padrão.
Perguntas frequentes
Como usar regex em C#?
Adicione using System.Text.RegularExpressions; e chame os métodos static de Regex: Regex.IsMatch(input, pattern) retorna um bool, Regex.Match retorna a primeira correspondência, Regex.Matches todas elas, Regex.Replace substitui e Regex.Split divide. Escreva os padrões como strings verbatim, @"\d+", para que as barras invertidas cheguem ao motor de regex sem mudança.
Como obter o valor de um grupo de uma correspondência regex em C#?
Coloque parênteses em volta da parte que você quer e leia match.Groups[1].Value (os grupos são numerados a partir de 1; o grupo 0 é a correspondência inteira). Grupos nomeados são mais claros: (?<year>\d{4}) é lido com match.Groups["year"].Value. Verifique match.Success antes, porque uma correspondência que falhou tem grupos vazios, e não null.
Como substituir texto com regex em C#?
Regex.Replace(input, pattern, replacement) substitui todas as correspondências. A substituição pode se referir a grupos: $1 para um grupo numerado, ${name} para um nomeado, $0 para a correspondência inteira. Para uma lógica que um modelo não consegue expressar, passe uma lambda: Regex.Replace(text, @"\d+", m => (int.Parse(m.Value) * 2).ToString()).
Como fazer uma regex em C# ignorar maiúsculas e minúsculas?
Passe RegexOptions.IgnoreCase: Regex.IsMatch(input, "error", RegexOptions.IgnoreCase). As opções se combinam com |, por exemplo RegexOptions.IgnoreCase | RegexOptions.Multiline. Você também pode ativá-la dentro do padrão com (?i).
Como validar um endereço de e-mail com regex em C#?
Um padrão como ^[^@\s]+@[^@\s]+\.[^@\s]+$ pega erros de digitação óbvios (falta de @, espaços, domínio sem ponto) e normalmente é tudo de que um formulário precisa. Uma regex não consegue validar de verdade um endereço: a gramática completa permite formas que nenhum padrão prático trata, e um endereço sintaticamente válido pode não existir. Verifique a forma básica e depois confirme enviando um e-mail de verificação.