Un'espressione regolare (regex) è un pattern che descrive del testo: "quattro cifre, un trattino, due cifre", "una parola che inizia con una maiuscola", "qualsiasi cosa tra parentesi quadre". In C#, la classe Regex di System.Text.RegularExpressions trova, estrae, sostituisce e divide il testo con pattern di questo tipo.
IsMatch, Match e Matches
Tre metodi coprono la maggior parte delle ricerche. IsMatch risponde sì o no, Match restituisce la prima corrispondenza e Matches le restituisce tutte:
Output:
True
2026-03-14 at index 22
4 matches:
1042
2026
1043
2026
Success: False, Value: []
Un Match fallito non restituisce null: restituisce un Match il cui Success è false e il cui Value è vuoto. Verifica sempre Success prima di usare una corrispondenza.
La ricerca di quattro cifre mostra come i pattern catturino più di quanto previsto. \b\d{4}\b significa "esattamente quattro cifre come parola intera", e gli anni dentro le date rientrano, perché - è un confine di parola. Restringi il pattern per dire ciò che intendi, per esempio (?<=Order |order )\d+, oppure usa i gruppi, che vediamo tra poco.
Stringhe verbatim per i pattern
La sintassi delle regex usa backslash ovunque (\d, \s, \b), e lo stesso fanno le sequenze di escape delle stringhe C#. In una stringa normale, "\d" non compila nemmeno (CS1009, sequenza di escape non riconosciuta) e "\b" viene compilato in un carattere backspace, cambiando il pattern senza avvisi. Scrivi i pattern come stringhe verbatim con @, dove un backslash è solo un backslash:
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
In una stringa verbatim, le virgolette doppie si scrivono "". I raw string literal di C# 11 ("""...""") evitano anche questo.
Gruppi: estrarre parti di una corrispondenza
Le parentesi creano un gruppo, e il testo di ogni gruppo è disponibile dopo la corrispondenza. I gruppi sono numerati da 1 nell'ordine della loro parentesi di apertura; il gruppo 0 è l'intera corrispondenza. I gruppi con nome, (?<name>...), sono più facili da leggere e sopravvivono alle modifiche del pattern:
Output:
2026-03-14
ERROR
payments
Card declined for order 1042
60 by 90, area 5400
[ e ] hanno un significato nelle regex (una classe di caratteri), quindi per trovare le parentesi letterali servono \[ e \]. Lo stesso vale per . * + ? ( ) { } ^ $ | \. Per trovare letteralmente una stringa fornita dall'utente dentro un pattern, passala a Regex.Escape, che fa l'escape dei metacaratteri per te: Regex.Escape("price (USD)") restituisce price\ \(USD\) (anche gli spazi vengono protetti, cosa innocua).
Le parentesi che raggruppano soltanto, senza catturare, si scrivono (?:...). Mantengono pulita la numerazione dei gruppi e sono leggermente più veloci.
Replace: sostituzioni e lambda
Regex.Replace sostituisce ogni corrispondenza. Nella stringa di sostituzione, $1 inserisce il gruppo 1, ${name} un gruppo con nome e $0 l'intera corrispondenza. Per tutto ciò che un modello non può esprimere, passa una funzione (di solito una lambda) che riceve ogni Match e restituisce la sua sostituzione:
Output:
Invoiced 14/03/2026, paid 02/04/2026.
too many spaces
Card **** **** **** 1234
Tea 2.75, Scone 3.52, Jam 1.10
La maschera della carta usa un lookahead, (?=...): trova una cifra solo se è seguita da altre quattro cifre, senza consumarle. I lookaround ((?=...), (?!...), (?<=...), (?<!...)) verificano il contesto senza includerlo nella corrispondenza, ed è questo che permette a un solo Replace di lasciare intatte le ultime quattro cifre.
Per testo semplice senza pattern, string.Replace è più semplice e più veloce; passa a Regex.Replace quando la cosa da sostituire varia.
Split
Regex.Split divide a ogni corrispondenza di un pattern, e questo gestisce separatori che variano:
Output:
csharp / dotnet / regex / tutorial / beginner
Order Shipped Event Handler
Se il pattern contiene gruppi di cattura, Regex.Split include nel risultato i separatori catturati; usa (?:...) quando non li vuoi.
RegexOptions
Le opzioni cambiano il comportamento dell'intero pattern. Combinale con |:
Output:
1
2
error INFO Error
True
Quelle che userai:
IgnoreCase: corrispondenza non sensibile alle maiuscole (forma inline(?i)).Multiline:^e$corrispondono all'inizio e alla fine di ogni riga, non solo dell'intera stringa.Singleline:.corrisponde anche a\n(di default corrisponde a qualsiasi carattere tranne il ritorno a capo).IgnorePatternWhitespace: gli spazi nel pattern vengono ignorati e#inizia un commento, per pattern lunghi scritti su più righe.CultureInvariant: conIgnoreCase, confronta senza le regole della cultura corrente.Compiled: compila il pattern in IL una volta sola, per una regex usata molte volte.
Validare l'input: ancore e la questione delle email
Per la validazione, ancora il pattern con ^ e $, altrimenti passa non appena una parte dell'input corrisponde:
Output:
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
Il pattern per le email controlla solo la forma: qualcosa senza spazi né @, una @, un dominio con un punto. È voluto. La vera grammatica degli indirizzi (RFC 5322) ammette parti locali tra virgolette, commenti e domini con indirizzo IP letterale; i pattern che provano a coprirla arrivano a centinaia di caratteri e rifiutano comunque indirizzi validi che le persone usano davvero. E un indirizzo sintatticamente perfetto può comunque essere rifiutato dal server. Controlla la forma, poi invia un'email di conferma. System.Net.Mail.MailAddress offre un altro controllo della forma se preferisci non scrivere un pattern.
Prestazioni: metodi statici, istanze, compilazione
I metodi statici (Regex.IsMatch(input, pattern)) analizzano il pattern e lo conservano in una piccola cache (15 pattern di default), quindi le chiamate ripetute con lo stesso pattern costano poco. Per un pattern usato in un ciclo critico, crea un'unica istanza Regex e tienila in un campo static readonly; aggiungi RegexOptions.Compiled se viene eseguita migliaia di volte, scambiando un avvio più lento con corrispondenze più veloci.
Timeout e backtracking catastrofico
Alcuni pattern richiedono un tempo esponenziale su certi input. Il classico è un quantificatore annidato come ^(a+)+$ contro "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!": il motore prova tutti i modi di dividere le a tra i due + prima di arrendersi. Su un server web, un solo input del genere inviato da un utente può tenere occupato un core della CPU (un attacco "ReDoS"). Dai un timeout a ogni regex che gestisce input non affidabile:
var pattern = new Regex(@"^(\w+\s?)*$", RegexOptions.None, TimeSpan.FromMilliseconds(100));
try
{
bool ok = pattern.IsMatch(userInput);
}
catch (RegexMatchTimeoutException)
{
// treat as invalid input
}
Anche ogni metodo statico ha un overload che accetta un timeout. .NET 7 ha aggiunto anche RegexOptions.NonBacktracking, un motore che garantisce un tempo lineare al prezzo di alcune funzionalità (lookaround, backreference).
GeneratedRegex (.NET 7)
.NET 7 ha aggiunto un source generator che trasforma un pattern in normale codice C# in fase di compilazione. Ottieni la velocità di Compiled senza costi di avvio, il codice generato è leggibile nell'IDE e funziona con il 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
Su .NET 7 e successivi questa è la forma consigliata per qualsiasi pattern noto in fase di compilazione.
Errori comuni
- Ancore mancanti nella validazione. Senza
^...$,IsMatchaccetta un input che si limita a contenere una corrispondenza. - Stringhe normali per i pattern.
"\b"è un backspace, non un confine di parola. Usa@"...". - Caratteri speciali senza escape.
.corrisponde a qualsiasi carattere;3.50come pattern corrisponde anche a3x50. Fai l'escape con\., oppure conRegex.Escapeper l'input dell'utente. - Quantificatori greedy.
<.*>su<b>bold</b>corrisponde all'intera stringa. Usa il lazy.*?o una classe negata[^>]*. - Nessun timeout sull'input non affidabile. Un quantificatore annidato può bloccare una richiesta.
- Regex per formati strutturati. HTML, JSON e CSV con virgolette richiedono un parser, non un pattern.
Domande frequenti
Come si usano le regex in C#?
Aggiungi using System.Text.RegularExpressions; e chiama i metodi statici di Regex: Regex.IsMatch(input, pattern) restituisce un bool, Regex.Match restituisce la prima corrispondenza, Regex.Matches tutte, Regex.Replace sostituisce e Regex.Split divide. Scrivi i pattern come stringhe verbatim, @"\d+", così i backslash arrivano invariati al motore delle regex.
Come ottengo il valore di un gruppo da una corrispondenza regex in C#?
Metti tra parentesi la parte che ti interessa e leggi match.Groups[1].Value (i gruppi sono numerati da 1; il gruppo 0 è l'intera corrispondenza). I gruppi con nome sono più chiari: (?<year>\d{4}) si legge con match.Groups["year"].Value. Controlla prima match.Success, perché una corrispondenza fallita ha gruppi vuoti invece che null.
Come sostituisco del testo con una regex in C#?
Regex.Replace(input, pattern, replacement) sostituisce ogni corrispondenza. La sostituzione può fare riferimento ai gruppi: $1 per un gruppo numerato, ${name} per uno con nome, $0 per l'intera corrispondenza. Per una logica che un modello non può esprimere, passa una lambda: Regex.Replace(text, @"\d+", m => (int.Parse(m.Value) * 2).ToString()).
Come rendo una regex C# non sensibile alle maiuscole?
Passa RegexOptions.IgnoreCase: Regex.IsMatch(input, "error", RegexOptions.IgnoreCase). Le opzioni si combinano con |, per esempio RegexOptions.IgnoreCase | RegexOptions.Multiline. Puoi anche attivarla dentro il pattern con (?i).
Come valido un indirizzo email con una regex in C#?
Un pattern come ^[^@\s]+@[^@\s]+\.[^@\s]+$ intercetta gli errori di battitura evidenti (@ mancante, spazi, nessun punto nel dominio) e di solito è tutto ciò che serve a un modulo. Una regex non può validare davvero un indirizzo: la grammatica completa ammette forme che nessun pattern pratico gestisce, e un indirizzo sintatticamente valido potrebbe non esistere. Controlla la forma di base, poi conferma inviando un'email di verifica.