Регулярные выражения — один из тех инструментов, которые либо любят, либо обходят стороной. Но для разработчика на C# они незаменимы: проверка email, разбор логов, извлечение данных из текста, очистка строк. Правильно написанный шаблон заменяет десятки строк кода с циклами и условиями.
В .NET за работу с регулярками отвечает пространство имён System.Text.RegularExpressions. Разберём основные возможности и подводные камни.
Первое знакомство с Regex
Центральный класс — Regex. Самый простой сценарий — проверить, соответствует ли строка шаблону.
using System.Text.RegularExpressions;
bool ok = Regex.IsMatch("abc123", @"^[a-z]+\d+$");
Console.WriteLine(ok); // True
Обратите внимание на префикс @ перед строкой. Это буквальная (verbatim) строка: в ней обратная косая черта не требует удвоения. Без неё шаблон \d+ пришлось бы писать как "\\d+", и читать такие выражения очень тяжело.
Основы синтаксиса
Прежде чем писать сложные шаблоны, нужно выучить небольшой набор базовых элементов.
.— любой символ, кроме перевода строки.\d— цифра,\w— буква, цифра или подчёркивание,\s— пробельный символ. Заглавные версии (\D,\W,\S) означают обратное.^и$— начало и конец строки.[abc]— один из перечисленных символов,[^abc]— любой, кроме них,[a-z]— диапазон.*— ноль или больше повторений,+— одно и больше,?— ноль или одно.{n},{n,m}— точное число или диапазон повторений.|— альтернатива («или»).( )— группа.
Квантификаторы по умолчанию жадные: они захватывают как можно больше текста. Добавление вопросительного знака (.*?) делает их ленивыми, и они берут минимально возможное. Эта разница — источник самых частых ошибок при разборе HTML и логов.
Поиск совпадений
Метод Match возвращает первое совпадение, а Matches — все.
string text = "Звоните: 8-900-123-45-67 или 8-911-765-43-21";
var rx = new Regex(@"8-\d{3}-\d{3}-\d{2}-\d{2}");
foreach (Match m in rx.Matches(text))
{
Console.WriteLine($"{m.Value} (позиция {m.Index})");
}
У объекта Match есть свойства Value (найденный текст), Index (позиция), Length и Success. Всегда проверяйте Success, прежде чем читать результат одиночного Match.
Группы и именованные группы
Скобки позволяют не только группировать, но и извлекать части совпадения. Номера групп идут по порядку открывающих скобок, а нулевая группа — это всё совпадение целиком.
var rx = new Regex(@"(\d{2})\.(\d{2})\.(\d{2})");
var m = rx.Match("Релиз 15.03.24 выпущен");
if (m.Success)
{
Console.WriteLine(m.Groups[1].Value); // 15
Console.WriteLine(m.Groups[2].Value); // 03
}
Нумерованные группы быстро становятся нечитаемыми. Именованные решают проблему:
var rx = new Regex(@"(?<day>\d{2})\.(?<month>\d{2})\.(?<yy>\d{2})");
var m = rx.Match("15.03.24");
Console.WriteLine(m.Groups["month"].Value); // 03
Если группа нужна только для структуры и её значение не пригодится, используйте незахватывающую форму (?:...). Она работает быстрее и не засоряет нумерацию.
Замена текста
Метод Regex.Replace умеет подставлять группы в результат через $1 или ${name}.
string s = "Иванов Иван";
string r = Regex.Replace(s, @"(\w+) (\w+)", "$2 $1");
Console.WriteLine(r); // Иван Иванов
Для сложной логики подойдёт делегат MatchEvaluator, который получает совпадение и возвращает строку-замену:
string r = Regex.Replace("цена 10 и 20", @"\d+",
m => (int.Parse(m.Value) * 2).ToString());
// "цена 20 и 40"
Разбиение строк
Если разделитель нестандартный или их несколько, string.Split не справится, а Regex.Split подойдёт идеально:
string[] parts = Regex.Split("a, b;c d", @"[,;\s]+");
// ["a", "b", "c", "d"]
Опции RegexOptions
Поведение шаблона настраивается флагами. Самые полезные:
| Опция | Что делает |
|---|---|
| IgnoreCase | Игнорирует регистр букв |
| Multiline | ^ и $ работают на границах каждой строки |
| Singleline | Точка совпадает и с переводом строки |
| IgnorePatternWhitespace | Разрешает пробелы и комментарии внутри шаблона |
| Compiled | Компилирует шаблон для ускорения при частом использовании |
| CultureInvariant | Отключает зависимость от региональных настроек |
Опция IgnorePatternWhitespace спасает читаемость сложных выражений:
var rx = new Regex(@"
^(?<user>[\w.+-]+) # имя пользователя
@
(?<host>[\w-]+\.[\w.-]+)$ # домен
", RegexOptions.IgnorePatternWhitespace);
Производительность и безопасность
Регулярка может стать причиной серьёзных проблем с быстродействием. Главная угроза — катастрофический откат (backtracking). Вложенные квантификаторы вроде (a+)+ на неудачных входных данных заставляют движок перебирать экспоненциальное число вариантов, и процессор зависает.
Что делать:
- Избегайте вложенных повторений с пересекающимися множествами символов.
- Задавайте таймаут при создании:
new Regex(pattern, options, TimeSpan.FromSeconds(1)). По истечении будет выброшено исключениеRegexMatchTimeoutException. - Не создавайте объект
Regexв цикле. Вынесите его в статическое поле и переиспользуйте. - Для шаблонов, которые вызываются очень часто, применяйте
RegexOptions.Compiledили генераторы исходного кода, доступные в новых версиях платформы. - Если на входе приходит пользовательский текст, всегда ограничивайте его длину перед проверкой.
Атомарные группы (?>...) и притяжательные конструкции запрещают откат и помогают ускорить поиск там, где он точно не нужен.
Типичные ловушки
- Забытые якоря. Без
^и$проверка email найдёт адрес внутри любой строки мусора и вернёт true. - Неэкранированная точка. Шаблон
a.bсовпадёт и с «axb». Чтобы искать именно точку, пишите\.. - Разбор HTML регулярками. Для вложенных структур используйте специальные парсеры, а не регулярные выражения.
- Идеальная проверка email. Полноценный шаблон по стандарту огромен. На практике достаточно простой проверки и отправки письма с подтверждением.
- Кириллица и \w. В .NET класс
\wпонимает юникод, так что русские буквы проходят. Но в других средах это может отличаться.
Практический пример: разбор лога
Допустим, есть строки вида [ERROR] 14:25:03 Не удалось подключиться к базе. Нужно вытащить уровень, время и сообщение.
var rx = new Regex(
@"^\[(?<level>\w+)\]\s+(?<time>\d{2}:\d{2}:\d{2})\s+(?<msg>.+)$",
RegexOptions.Compiled);
foreach (var line in File.ReadLines("app.log"))
{
var m = rx.Match(line);
if (!m.Success) continue;
Console.WriteLine($"{m.Groups["level"].Value} | {m.Groups["time"].Value}");
}
Инструменты для отладки
Не пытайтесь отлаживать шаблон вслепую. Используйте онлайн-тестеры регулярных выражений, которые подсвечивают группы и показывают пошаговое выполнение. Сохраняйте набор тестовых строк — и подходящих, и неподходящих — и оформляйте их в модульные тесты. Регулярка без тестов быстро превращается в чёрный ящик, который боятся трогать.
Заключение
Регулярные выражения в C# — это компромисс между мощью и читаемостью. Используйте именованные группы, комментируйте шаблоны через IgnorePatternWhitespace, задавайте таймауты и не создавайте Regex в горячих циклах. Там, где достаточно string.Contains или Split, регулярки не нужны вообще. Но когда задача действительно требует гибкого поиска, этот инструмент сделает работу в разы короче и надёжнее.