Перейти к основному содержимому

5.05. Регулярные выражения

Разработчику Архитектору

Регулярные выражения

Регулярные выражения — это мощный инструмент для описания шаблонов текста. Они позволяют задавать правила, по которым можно находить, проверять и извлекать фрагменты строк. В программировании регулярные выражения используются повсеместно: от простой валидации email-адреса до сложного анализа логов или преобразования форматов данных. В языке C# этот механизм реализован в пространстве имён System.Text.RegularExpressions и предоставляет разработчику гибкие средства для работы с текстом на основе шаблонов.

Основная идея

Регулярное выражение представляет собой последовательность символов, составляющую шаблон поиска. Этот шаблон может включать как обычные символы (например, буквы или цифры), так и специальные метасимволы, которые определяют правила сопоставления. Когда регулярное выражение применяется к строке, система проверяет, содержит ли строка подстроку, соответствующую заданному шаблону. Если соответствие найдено, можно получить информацию о позиции совпадения, его длине, а также извлечь конкретные части строки, соответствующие отдельным элементам шаблона.

В C# основной класс для работы с регулярными выражениями — Regex. Он позволяет компилировать шаблон один раз и многократно использовать его для поиска, замены или проверки строк. Это особенно полезно в сценариях, где одно и то же выражение применяется к большому количеству данных, поскольку компиляция происходит единожды, а последующие операции выполняются быстро.

Типичные задачи

Регулярные выражения решают две ключевые задачи: валидацию и извлечение.

Валидация — это проверка того, соответствует ли вся строка заданному шаблону. Например, при регистрации пользователя на сайте необходимо убедиться, что введённый email имеет корректный формат. Регулярное выражение может описать структуру допустимого email-адреса, и если строка полностью совпадает с этим шаблоном, она считается валидной. В C# для такой проверки часто используется метод Regex.IsMatch, который возвращает логическое значение: true, если совпадение найдено, и false в противном случае.

Извлечение — это поиск и выделение конкретных частей строки, соответствующих определённым фрагментам шаблона. Например, из строки лога вида "2026-01-22 14:30:05 [INFO] User login successful" можно извлечь дату, время, уровень логирования и сообщение. Для этого в регулярном выражении используются группы — участки шаблона, заключённые в круглые скобки. Каждая группа захватывает свою часть совпадения, и после выполнения поиска можно обратиться к этим частям по индексу или имени.

Квантификаторы

Квантификаторы определяют, сколько раз может повторяться предшествующий элемент шаблона. Они позволяют описывать не только фиксированные последовательности, но и вариативные конструкции.

Символ * означает, что предшествующий элемент может встречаться ноль или более раз. Например, шаблон a* совпадает со строками "" (пустая строка), "a", "aa", "aaa" и так далее. Этот квантификатор полезен, когда элемент необязателен, но может повторяться произвольное количество раз.

Символ + указывает, что предшествующий элемент должен встречаться один или более раз. Шаблон a+ не совпадает с пустой строкой, но совпадает с "a", "aa", "aaa". Этот квантификатор применяется, когда элемент обязателен, но его количество не фиксировано.

Символ ? означает, что предшествующий элемент может встречаться ноль или один раз. То есть элемент необязателен, но если он присутствует, то только в единственном экземпляре. Например, шаблон colou?r совпадает как с "color", так и с "colour", что удобно при работе с британским и американским написанием слов.

Фигурные скобки {n,m} задают точное или диапазонное количество повторений. Выражение {n} означает ровно n повторений, {n,} — не менее n повторений, а {n,m} — от n до m повторений включительно. Например, шаблон \d{4} описывает последовательность из четырёх цифр, что подходит для года или PIN-кода. Шаблон \d{3,5} совпадает с последовательностью из трёх, четырёх или пяти цифр.

Квантификаторы по умолчанию являются «жадными» — они стремятся захватить максимально возможную часть строки. В некоторых случаях требуется «ленивое» поведение, при котором захватывается минимально возможная часть. Для этого после квантификатора добавляется символ ?, например *?, +?, {n,m}?. Это особенно важно при работе с многострочными текстами или вложенной структурой данных.

Группы

Группы — это механизм захвата и выделения частей совпадения. Любая часть регулярного выражения, заключённая в круглые скобки (), становится группой. После выполнения поиска каждая группа сохраняет ту часть строки, которая ей соответствовала. Группы нумеруются слева направо, начиная с единицы. Нулевая группа всегда содержит всё совпадение целиком.

Например, рассмотрим шаблон (\d{2})-(\d{2})-(\d{4}) для даты в формате ДД-ММ-ГГГГ. При применении к строке "22-01-2026" первая группа захватит "22", вторая — "01", третья — "2026". Это позволяет легко разобрать дату на компоненты без дополнительных операций со строкой.

В C# результат поиска возвращается в виде объекта Match. У этого объекта есть свойство Groups, которое предоставляет доступ ко всем захваченным группам. Элементы коллекции Groups можно получать по индексу: match.Groups[1].Value вернёт значение первой группы.

Помимо нумерованных групп, C# поддерживает именованные группы. Они позволяют обращаться к захваченным данным не по номеру, а по смысловому имени. Именованная группа записывается как (?<имя>шаблон). Например, шаблон (?<день>\d{2})-(?<месяц>\d{2})-(?<год>\d{4}) создаёт три группы с именами день, месяц и год. После поиска можно получить значение дня как match.Groups["день"].Value. Это делает код более читаемым и устойчивым к изменениям: если порядок групп в шаблоне изменится, обращение по имени останется корректным.

Именованные группы особенно полезны в сложных шаблонах, где количество групп велико, а их назначение неочевидно из контекста. Они также упрощают поддержку кода, поскольку имя группы сразу объясняет, какую информацию она содержит.

Специальные символы и экранирование

Регулярные выражения используют множество специальных символов, таких как ., *, +, ?, ^, $, [], {}, (), |, \. Эти символы имеют особое значение и не интерпретируются как обычные знаки. Например, точка . означает любой символ, кроме перевода строки, а символ ^ обозначает начало строки.

Если требуется найти именно символ * или ( в тексте, его нужно экранировать с помощью обратной косой черты: \*, \(. В C# это требует особого внимания, поскольку сам язык использует обратную косую черту как escape-символ в строковых литералах. Чтобы избежать двойного экранирования, рекомендуется использовать строковые литералы с префиксом @, например: @"\d+\.\d+" — такой шаблон ищет десятичную дробь, например "3.14".

Практическое применение в C#

В C# работа с регулярными выражениями начинается с создания экземпляра класса Regex. Можно передать шаблон в конструктор, а затем вызывать методы IsMatch, Match, Matches или Replace. Альтернативно, можно использовать статические методы класса Regex, которые принимают шаблон и строку напрямую. Однако при многократном использовании одного шаблона предпочтительнее создавать объект Regex, так как это позволяет избежать повторной компиляции.

Метод IsMatch проверяет наличие хотя бы одного совпадения в строке. Он полезен для валидации: если вся строка должна соответствовать шаблону, шаблон оформляется с привязкой к началу (^) и концу ($) строки, например: ^\d{4}-\d{2}-\d{2}$.

Метод Match возвращает первое совпадение в виде объекта Match. Если совпадений нет, возвращается объект, для которого свойство Success равно false. Метод Matches возвращает коллекцию всех совпадений, что полезно при анализе логов или извлечении множественных значений из текста.

Метод Replace позволяет заменить все совпадения на указанную строку или результат функции. Это мощный инструмент для трансформации текста: например, можно заменить все email-адреса в документе на заглушки или преобразовать формат даты.

Производительность и компиляция

Регулярные выражения в C# могут быть скомпилированы в промежуточный код .NET, что значительно ускоряет их выполнение при многократном использовании. По умолчанию шаблоны интерпретируются, но при создании объекта Regex можно указать флаг RegexOptions.Compiled. Это увеличивает время инициализации, но ускоряет последующие операции. Для одноразового использования компиляция нецелесообразна, но для часто вызываемых шаблонов — обязательна.

Также стоит учитывать, что сложные регулярные выражения могут приводить к экспоненциальному времени выполнения на некоторых входных данных — это явление известно как «катастрофический возврат». Чтобы избежать этого, следует проектировать шаблоны с учётом эффективности, избегать избыточных вложенных квантификаторов и использовать нежадные квантификаторы там, где это уместно.