1 / 33 • Регулярные выражения в C#
📘 Регулярные выражения в C#

System.Text.RegularExpressions — мощный инструмент для работы с текстом.

Лекция охватывает: основы, синтаксис, группы, опции, производительность, реальные примеры.

⏱ Продолжительность: ~1.5 часа (30+ слайдов)
🎯 Цель: научиться эффективно использовать Regex в C#.

📍 gadget59.ru — ваш гид в мире .NET

🔍 Что такое регулярные выражения?

Regex (Regular Expression) — это последовательность символов, задающая шаблон для поиска, замены и извлечения подстрок.

  • Поиск — найти все email, номера телефонов, даты
  • Валидация — проверить формат ввода (email, пароль, ИНН)
  • Замена — очистить текст от HTML-тегов, лишних пробелов
  • Сплит — разбить строку по сложному разделителю
// Пример: найти все цифры в строке
using System.Text.RegularExpressions;
var matches = Regex.Matches("abc123def456", @"\d+");
🧩 Пространство имен: System.Text.RegularExpressions

Основные типы:

  • Regex — статические и экземплярные методы
  • Match — результат одного совпадения
  • MatchCollection — коллекция всех совпадений
  • Group — группа захвата (часть шаблона в скобках)
  • Capture — отдельный захват в группе (для повторяющихся групп)
using System.Text.RegularExpressions;

string input = "Цена: 100 руб";
Match m = Regex.Match(input, @"\d+");
if (m.Success) Console.WriteLine(m.Value); // 100
⚡ IsMatch, Match, Matches — три ключевых метода
МетодОписание
Regex.IsMatch()Возвращает true, если шаблон найден
Regex.Match()Возвращает первое совпадение (тип Match)
Regex.Matches()Возвращает все совпадения (MatchCollection)
string text = "123 abc 456";
bool hasDigits = Regex.IsMatch(text, @"\d+"); // true
Match first = Regex.Match(text, @"\d+"); // "123"
MatchCollection all = Regex.Matches(text, @"\d+"); // ["123","456"]
📐 Синтаксис Regex (часть 1)
МетасимволЗначение
\dЦифра [0-9]
\DНе цифра
\wБуква, цифра или "_" [A-Za-z0-9_]
\WНе \w
\sПробельный символ
\SНе пробельный
.Любой символ, кроме \n (с опцией Singleline — включая \n)
📐 Синтаксис Regex (часть 2) — квантификаторы и якоря
СимволОписание
*0 или более раз
+1 или более раз
?0 или 1 раз
{n}ровно n раз
{n,}n или более раз
{n,m}от n до m раз
^Начало строки
$Конец строки
🧪 Примеры квантификаторов
// * — 0 или более
Regex.IsMatch("ab", "a*b") // true
// + — 1 или более цифр
Regex.Match("abc123", @"\d+").Value // "123"
// {3} — ровно 3 буквы
Regex.IsMatch("abc", @"^[a-z]{3}$") // true
// {2,4} — от 2 до 4 цифр
Regex.Match("12345", @"\d{2,4}").Value // "1234"

Важно Квантификаторы по умолчанию жадные (захватывают максимум).

⏳ Жадные vs Ленивые (?)

Жадный (по умолчанию) — захватывает максимально возможную длину.

Ленивый (добавить ? после квантификатора) — минимально возможную.

string html = "<div>A</div><div>B</div>";

// Жадный: захватит всё от первого <div> до последнего </div>
string greedy = Regex.Match(html, @"<div>.*</div>").Value;

// Ленивый: <div>A</div>
string lazy = Regex.Match(html, @"<div>.*?</div>").Value;
📦 Группы захвата ( )

Скобки ( ) создают группу, которую можно извлечь через match.Groups[index].

string input = "Date: 2024-12-31";
string pattern = @"(\d{4})-(\d{2})-(\d{2})";
Match m = Regex.Match(input, pattern);
if (m.Success) {
  Console.WriteLine(m.Groups[0].Value); // полное: "2024-12-31"
  Console.WriteLine(m.Groups[1].Value); // "2024"
  Console.WriteLine(m.Groups[2].Value); // "12"
  Console.WriteLine(m.Groups[3].Value); // "31"
}
🏷 Именованные группы (?<имя>...)

Группам можно давать имена для удобства.

string input = "User: john@example.com";
string pattern = @"User:\s*(?<email>\w+@\w+\.\w+)";
Match m = Regex.Match(input, pattern);
if (m.Success) {
  Console.WriteLine(m.Groups["email"].Value); // john@example.com
}

Совет Именованные группы делают код читаемее.

🚫 Несохраняемые группы (?:...)

Если нужно сгруппировать часть шаблона, но не создавать отдельную группу в Groups.

string input = "100 руб";
// (?: ) — не сохраняет группу
Match m = Regex.Match(input, @"(\d+)\s+(?:руб|USD)");
if (m.Success) {
  Console.WriteLine(m.Groups[1].Value); // "100"
  Console.WriteLine(m.Groups.Count); // 2 (группа 0 + группа 1)
}
⚙️ RegexOptions — управление поведением
ОпцияОписание
IgnoreCaseИгнорировать регистр
Multiline^ и $ работают с каждой строкой
SinglelineТочка (.) включает перевод строки
CompiledКомпилирует Regex в IL (быстрее при многократном использовании)
IgnorePatternWhitespaceИгнорирует пробелы и комментарии в шаблоне
Regex rx = new Regex(@"^\w+$",
  RegexOptions.IgnoreCase | RegexOptions.Compiled);
📝 Пример: IgnoreCase + Multiline
string text = "Hello\nworld\nHELLO";
MatchCollection matches = Regex.Matches(text, @"^hello$",
  RegexOptions.IgnoreCase | RegexOptions.Multiline);
// Найдёт "Hello" и "HELLO"

Multiline позволяет ^ и $ работать на каждой строке.

🔄 Замена текста: Regex.Replace

Заменяет все вхождения шаблона на указанную строку.

string input = "123 abc 456 xyz";
string result = Regex.Replace(input, @"\d+", "NUM");
// result: "NUM abc NUM xyz"

// Использование MatchEvaluator (динамическая замена)
string doubled = Regex.Replace(input, @"\d+", m =>
  int.Parse(m.Value) * 2 + "");
// doubled: "246 abc 912 xyz"
🧹 Практический пример: очистка HTML-тегов
string html = "<div>Hello <b>World</b></div>";
string clean = Regex.Replace(html, @"<[^>]*>", "");
// clean: "Hello World"

// Альтернатива: удалить теги, оставив пробелы
string clean2 = Regex.Replace(html, @"<[^>]*>", " ");
// clean2: " Hello World "
✂️ Regex.Split — разбиение по сложному разделителю
string text = "one,two;three four";
string[] parts = Regex.Split(text, @"[,;\s]+");
// parts: ["one", "two", "three", "four"]

string log = "ERROR: 404 | WARN: 503 | INFO: 200";
string[] codes = Regex.Split(log, @"\s*[|:]\s*");
// codes: ["ERROR", "404", "WARN", "503", "INFO", "200"]
📧 Валидация email

Простой, но надёжный шаблон для проверки email.

public static bool IsValidEmail(string email)
{
  string pattern = @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$";
  return Regex.IsMatch(email, pattern, RegexOptions.IgnoreCase);
}

Примечание Для полной валидации email используйте более сложный шаблон или библиотеку.

📞 Валидация телефона (Россия)
public static bool IsValidRussianPhone(string phone)
{
  // +7 (999) 123-45-67 или 8 999 123-45-67
  string pattern = @"^(\+7|8)\s*\(?\d{3}\)?\s*\d{3}[-\s]?\d{2}[-\s]?\d{2}$";
  return Regex.IsMatch(phone, pattern);
}
🌐 Извлечение URL из текста
string text = "Сайт: https://gadget59.ru и http://example.com";
string pattern = @"https?://[^\s/$.?#].[^\s]*";
MatchCollection urls = Regex.Matches(text, pattern);
foreach (Match m in urls)
  Console.WriteLine(m.Value);
// Вывод: https://gadget59.ru, http://example.com
⚡ Компилированные Regex (RegexOptions.Compiled)

При многократном использовании (например, в цикле) компиляция даёт прирост скорости.

// Плохо (каждый раз перекомпилируется)
for (int i = 0; i < 1000; i++)
  Regex.IsMatch("123", @"\d+");

// Хорошо (один раз скомпилирован)
Regex compiled = new Regex(@"\d+", RegexOptions.Compiled);
for (int i = 0; i < 1000; i++)
  compiled.IsMatch("123");
⏱️ Regex.Timeout — предотвращение зависаний

Некоторые шаблоны могут приводить к экспоненциальному росту времени (catastrophic backtracking).

Regex rx = new Regex(@"^(\d+)+$",
  RegexOptions.None, TimeSpan.FromMilliseconds(200));

try {
  bool result = rx.IsMatch("12345678901234567890");
} catch (RegexMatchTimeoutException) {
  Console.WriteLine("Timeout!");
}
🧹 IgnorePatternWhitespace — читаемые шаблоны

Позволяет добавлять пробелы и комментарии в шаблон.

string pattern = @"
^ # начало строки
(\d{4}) # год
- (\d{2}) # месяц
- (\d{2}) # день
$"
;

Regex rx = new Regex(pattern,
  RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
🔁 Повторяющиеся группы: CaptureCollection

Если группа повторяется (например, (\d+)+), в Group.Captures хранятся все значения.

string input = "a1b22c333";
Match m = Regex.Match(input, @"(?:a|b|c)(\d+)+");
if (m.Success) {
  Group g = m.Groups[1];
  Console.WriteLine(g.Value); // "333" (последнее)
  foreach (Capture c in g.Captures)
    Console.WriteLine(c.Value); // "1", "22", "333"
}
👁️ Lookahead: (?=) и (?!)

Позитивный lookahead (?=...) — проверяет, что после текущей позиции есть совпадение.

Негативный lookahead (?!...) — что нет совпадения.

// Найти "q", после которого идёт цифра
Regex.Match("q1 q2", "q(?=\\d)").Value // "q"

// Найти "q", после которого НЕ цифра
Regex.Match("qa q1", "q(?!\\d)").Value // "q" (перед 'a')
👁️ Lookbehind: (?<=) и (?<!)

Позитивный lookbehind (?<=...) — проверяет, что перед текущей позицией есть совпадение.

Негативный lookbehind (?<!...) — что нет совпадения.

// Найти цифры после "Цена: "
Regex.Match("Цена: 100 руб", "(?<=Цена: )\\d+").Value // "100"

// Найти "foo", перед которым не "bar"
Regex.Match("barfoo foofoo", "(?<!bar)foo").Value // "foo" (второе)
📋 Парсинг логов: IP-адрес + дата
string logLine = "192.168.1.1 - - [15/Feb/2024:12:34:56] GET /index.html";
string pattern = @"(\d+\.\d+\.\d+\.\d+).*\[(.*?)\]";
Match m = Regex.Match(logLine, pattern);
if (m.Success) {
  Console.WriteLine($"IP: {m.Groups[1].Value}");
  Console.WriteLine($"Date: {m.Groups[2].Value}");
}
🌍 Unicode и кириллица в Regex

Стандартные \w, \d работают с ASCII. Для кириллицы используйте категории Unicode.

// Русские буквы: \p{L} (любая буква) или \p{IsCyrillic}
string text = "Привет 123";
Match m = Regex.Match(text, @"\p{IsCyrillic}+");
Console.WriteLine(m.Value); // "Привет"

// Или диапазон [А-Яа-я]
Match m2 = Regex.Match(text, @"[А-Яа-я]+");
🔄 Статические vs экземплярные методы
СпособПримерКогда использовать
СтатическийRegex.IsMatch(text, pattern)Одноразовое использование
Экземплярныйnew Regex(pattern).IsMatch(text)Многократное использование, особенно с Compiled
// Статический (удобно для одного вызова)
bool b1 = Regex.IsMatch("abc", @"^[a-z]+$");

// Экземплярный (для повторного использования)
Regex rx = new Regex(@"^[a-z]+$", RegexOptions.Compiled);
bool b2 = rx.IsMatch("abc");
⚠️ Частые ошибки с Regex
  • Забыли экранировать — точки, скобки, звёздочки: \., \(, \*
  • Жадность — используйте ? для ленивого поиска
  • Catastrophic backtracking — избегайте вложенных квантификаторов (например, (a+)+)
  • Неверные группы — Groups[0] — это всё совпадение, Groups[1] — первая скобка
  • Сравнение с пустой строкой — Regex.IsMatch("", ".*") вернёт true
🚀 Производительность Regex
  • Используйте Compiled при многократном использовании
  • Устанавливайте Timeout для критических операций
  • Предпочитайте статические методы для редких вызовов
  • Избегайте избыточных групп — применяйте (?:)
  • Кэшируйте скомпилированные Regex в статических полях
✅ Правило: если паттерн используется два и более раза — создайте static readonly Regex с Compiled.
🏢 Пример из реального проекта: парсинг CSV
// Парсинг строки CSV с учётом кавычек
string csvLine = "John, \"Doe, Jr.\", 25";
string pattern = @"(?:^|,)\s*(?:(?<quote>"")(?<content>.*?)\k<quote>|(?<content>[^,]*))";
MatchCollection fields = Regex.Matches(csvLine, pattern);
foreach (Match m in fields) {
  Console.WriteLine(m.Groups["content"].Value.Trim());
}
// Вывод: John, Doe, Jr., 25
⚠️ Альтернативы Regex (когда не нужно)
  • Обычные строковые методы — Contains, StartsWith, IndexOf (быстрее для простых поисков)
  • Парсеры — для HTML (HtmlAgilityPack), JSON (System.Text.Json), XML (XDocument)
  • Сложные грамматики — используйте парсер-комбинаторы (например, Sprache, Superpower)
  • Большие объёмы данных — регулярные выражения могут быть медленнее ручного перебора
🎯 Итоги и дополнительные ресурсы

Что мы рассмотрели:

  • Основы: методы IsMatch, Match, Matches, Replace, Split
  • Синтаксис: метасимволы, квантификаторы, якоря
  • Группы: ( ), именованные, несохраняемые
  • Опции: IgnoreCase, Compiled, Multiline, Singleline
  • Продвинутые: lookahead, lookbehind, тайм-аут, производительность
📚 Рекомендации:
— regex101.com (тестирование)
— Microsoft Docs: Regular Expressions in .NET
— gadget59.ru — больше материалов по C#

🤝 Спасибо за внимание! Вопросы?

🏠 На главную