Wyrażenie regularne (regex) to wzorzec opisujący tekst: "cztery cyfry, myślnik, dwie cyfry", "słowo zaczynające się wielką literą", "cokolwiek między nawiasami kwadratowymi". W C# klasa Regex z System.Text.RegularExpressions wyszukuje, wyciąga, zamienia i dzieli tekst przy użyciu takich wzorców.
IsMatch, Match i Matches
Trzy metody obsługują większość wyszukiwań. IsMatch odpowiada tak lub nie, Match zwraca pierwsze dopasowanie, a Matches wszystkie:
Wynik:
True
2026-03-14 at index 22
4 matches:
1042
2026
1043
2026
Success: False, Value: []
Nieudane Match nie zwraca null: zwraca Match, którego Success ma wartość false, a Value jest puste. Zawsze sprawdzaj Success przed użyciem dopasowania.
Wyszukiwanie czterech cyfr pokazuje, jak wzorce łapią więcej, niż zamierzano. \b\d{4}\b oznacza "dokładnie cztery cyfry jako całe słowo", a lata w datach się kwalifikują, bo - to granica słowa. Zawęź wzorzec, żeby mówił dokładnie to, o co ci chodzi, na przykład (?<=Order |order )\d+, albo użyj grup, o których dalej.
Stringi dosłowne dla wzorców
Składnia regex wszędzie używa ukośników wstecznych (\d, \s, \b), podobnie jak sekwencje ucieczki w stringach C#. W zwykłym stringu "\d" nawet się nie kompiluje (CS1009, nierozpoznana sekwencja ucieczki), a "\b" kompiluje się do znaku backspace i po cichu zmienia wzorzec. Zapisuj wzorce jako stringi dosłowne z @, w których ukośnik wsteczny to po prostu ukośnik wsteczny:
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
W stringu dosłownym cudzysłów zapisuje się jako "". Surowe literały stringów z C# 11 ("""...""") pozwalają uniknąć nawet tego.
Grupy: wyciąganie części dopasowania
Nawiasy tworzą grupę, a tekst każdej grupy jest dostępny po dopasowaniu. Grupy są numerowane od 1 w kolejności nawiasów otwierających; grupa 0 to całe dopasowanie. Grupy nazwane, (?<name>...), łatwiej czytać i przetrwają zmiany we wzorcu:
Wynik:
2026-03-14
ERROR
payments
Card declined for order 1042
60 by 90, area 5400
[ i ] mają w regex znaczenie (klasa znaków), więc dopasowanie dosłownych nawiasów wymaga \[ i \]. To samo dotyczy . * + ? ( ) { } ^ $ | \. Żeby dopasować dosłownie string podany przez użytkownika wewnątrz wzorca, przepuść go przez Regex.Escape, który sam poprzedza metaznaki ukośnikami: Regex.Escape("price (USD)") zwraca price\ \(USD\) (spacje też są poprzedzone ukośnikiem, co jest nieszkodliwe).
Nawiasy, które tylko grupują, bez przechwytywania, zapisuje się jako (?:...). Utrzymują porządek w numeracji grup i są nieco szybsze.
Replace: podstawienia i lambdy
Regex.Replace zastępuje każde dopasowanie. W tekście zastępczym $1 wstawia grupę 1, ${name} grupę nazwaną, a $0 całe dopasowanie. Dla wszystkiego, czego szablon nie wyrazi, przekaż funkcję (zwykle lambdę), która dostaje każdy Match i zwraca jego zamiennik:
Wynik:
Invoiced 14/03/2026, paid 02/04/2026.
too many spaces
Card **** **** **** 1234
Tea 2.75, Scone 3.52, Jam 1.10
Maska karty używa lookahead, (?=...): dopasowuje cyfrę tylko wtedy, gdy następują po niej jeszcze cztery cyfry, bez ich konsumowania. Asercje otoczenia ((?=...), (?!...), (?<=...), (?<!...)) sprawdzają kontekst bez włączania go do dopasowania, i właśnie dlatego jedno Replace może zostawić ostatnie cztery cyfry nietknięte.
Dla zwykłego tekstu bez wzorca string.Replace jest prostsze i szybsze; po Regex.Replace sięgaj wtedy, gdy to, co trzeba zastąpić, się zmienia.
Split
Regex.Split dzieli tekst przy każdym dopasowaniu wzorca, co radzi sobie z różnymi separatorami:
Wynik:
csharp / dotnet / regex / tutorial / beginner
Order Shipped Event Handler
Jeśli wzorzec zawiera grupy przechwytujące, Regex.Split dołącza przechwycone separatory do wyniku; użyj (?:...), gdy ich nie chcesz.
RegexOptions
Opcje zmieniają zachowanie całego wzorca. Łącz je przez |:
Wynik:
1
2
error INFO Error
True
Te, których będziesz używać:
IgnoreCase: dopasowanie bez rozróżniania wielkości liter (forma wewnątrz wzorca:(?i)).Multiline:^i$pasują na początku i końcu każdej linii, a nie tylko całego stringu.Singleline:.pasuje też do\n(domyślnie pasuje do każdego znaku poza znakiem nowej linii).IgnorePatternWhitespace: spacje we wzorcu są ignorowane, a#rozpoczyna komentarz, co przydaje się przy długich wzorcach pisanych w kilku liniach.CultureInvariant: zIgnoreCaseporównuje bez reguł bieżącej kultury.Compiled: kompiluje wzorzec raz do IL, dla regexa używanego wiele razy.
Walidacja danych: kotwice i kwestia e-maila
Przy walidacji zakotwicz wzorzec przez ^ i $, inaczej przejdzie, gdy tylko część danych pasuje:
Wynik:
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
Wzorzec e-maila sprawdza tylko kształt: coś bez spacji ani @, jedno @, domena z kropką. To celowe. Prawdziwa gramatyka adresów (RFC 5322) dopuszcza części lokalne w cudzysłowach, komentarze i domeny w postaci adresów IP; wzorce, które próbują ją pokryć, rozrastają się do setek znaków i nadal odrzucają poprawne adresy, których ludzie naprawdę używają. A adres idealny składniowo i tak może zostać odrzucony przy wysyłce. Sprawdź kształt, a potem wyślij e-mail z potwierdzeniem. System.Net.Mail.MailAddress oferuje inne sprawdzenie kształtu, jeśli wolisz nie pisać wzorca.
Wydajność: metody statyczne, instancje, kompilacja
Metody statyczne (Regex.IsMatch(input, pattern)) parsują wzorzec i trzymają go w małym cache (domyślnie 15 wzorców), więc powtarzane wywołania z tym samym wzorcem są tanie. Dla wzorca używanego w gorącej pętli utwórz jedną instancję Regex i trzymaj ją w polu static readonly; dodaj RegexOptions.Compiled, jeśli wykonuje się tysiące razy, wymieniając wolniejszy start na szybsze dopasowywanie.
Limity czasu i katastrofalne nawroty
Niektóre wzorce na pewnych danych zajmują czas wykładniczy. Klasyka to zagnieżdżony kwantyfikator, na przykład ^(a+)+$ wobec "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!": silnik próbuje każdego sposobu podziału liter a między dwa +, zanim się podda. Na serwerze webowym jedno takie wejście od użytkownika może zająć cały rdzeń procesora (atak "ReDoS"). Każdemu regexowi, który obsługuje niezaufane dane, nadaj limit czasu:
var pattern = new Regex(@"^(\w+\s?)*$", RegexOptions.None, TimeSpan.FromMilliseconds(100));
try
{
bool ok = pattern.IsMatch(userInput);
}
catch (RegexMatchTimeoutException)
{
// treat as invalid input
}
Każda metoda statyczna ma też przeciążenie przyjmujące limit czasu. .NET 7 dodał również RegexOptions.NonBacktracking, silnik gwarantujący czas liniowy kosztem niektórych funkcji (asercji otoczenia, odwołań wstecznych).
GeneratedRegex (.NET 7)
.NET 7 dodał generator źródeł, który w czasie kompilacji zamienia wzorzec w zwykły kod C#. Dostajesz szybkość Compiled bez kosztu przy starcie, wygenerowany kod da się przeczytać w IDE, a całość działa z trimmingiem i 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
W .NET 7 i nowszych to zalecana forma dla każdego wzorca znanego w czasie kompilacji.
Typowe błędy
- Brak kotwic przy walidacji. Bez
^...$IsMatchakceptuje dane, które jedynie zawierają dopasowanie. - Zwykłe stringi dla wzorców.
"\b"to backspace, a nie granica słowa. Używaj@"...". - Niepoprzedzone ukośnikiem znaki specjalne.
.pasuje do dowolnego znaku;3.50jako wzorzec pasuje też do3x50. Pisz\.albo użyjRegex.Escapedla danych od użytkownika. - Zachłanne kwantyfikatory.
<.*>na<b>bold</b>dopasowuje cały string. Użyj leniwego.*?albo zanegowanej klasy[^>]*. - Brak limitu czasu dla niezaufanych danych. Zagnieżdżony kwantyfikator może zawiesić żądanie.
- Regex do formatów strukturalnych. HTML, JSON i CSV z cudzysłowami wymagają parsera, a nie wzorca.
Najczęściej zadawane pytania
Jak używać regex w C#?
Dodaj using System.Text.RegularExpressions; i wywołuj statyczne metody Regex: Regex.IsMatch(input, pattern) zwraca bool, Regex.Match zwraca pierwsze dopasowanie, Regex.Matches wszystkie, Regex.Replace podstawia, a Regex.Split dzieli. Zapisuj wzorce jako stringi dosłowne, @"\d+", żeby ukośniki wsteczne trafiały do silnika regex bez zmian.
Jak pobrać wartość grupy z dopasowania regex w C#?
Otocz nawiasami interesującą cię część i odczytaj match.Groups[1].Value (grupy są numerowane od 1; grupa 0 to całe dopasowanie). Grupy nazwane są czytelniejsze: (?<year>\d{4}) odczytuje się przez match.Groups["year"].Value. Najpierw sprawdź match.Success, bo nieudane dopasowanie ma puste grupy, a nie null.
Jak zamienić tekst za pomocą regex w C#?
Regex.Replace(input, pattern, replacement) zastępuje każde dopasowanie. Tekst zastępczy może odwoływać się do grup: $1 dla grupy numerowanej, ${name} dla nazwanej, $0 dla całego dopasowania. Dla logiki, której szablon nie wyrazi, przekaż lambdę: Regex.Replace(text, @"\d+", m => (int.Parse(m.Value) * 2).ToString()).
Jak sprawić, by regex w C# ignorował wielkość liter?
Przekaż RegexOptions.IgnoreCase: Regex.IsMatch(input, "error", RegexOptions.IgnoreCase). Opcje łączy się przez |, na przykład RegexOptions.IgnoreCase | RegexOptions.Multiline. Możesz też włączyć tę opcję wewnątrz wzorca przez (?i).
Jak zwalidować adres e-mail za pomocą regex w C#?
Wzorzec w rodzaju ^[^@\s]+@[^@\s]+\.[^@\s]+$ wyłapuje oczywiste literówki (brak @, spacje, brak kropki w domenie) i zwykle wystarcza w formularzu. Regex nie potrafi naprawdę zwalidować adresu: pełna gramatyka dopuszcza formy, których nie obsłuży żaden praktyczny wzorzec, a poprawny składniowo adres może nie istnieć. Sprawdź podstawowy kształt, a potem potwierdź adres, wysyłając e-mail weryfikacyjny.