Добавить в корзинуПозвонить
Найти в Дзене
Программы от меня

Как я полюбил ад: C# регулярные выражения для чайников и мазохистов

Вы думаете, программист — это человек, который спокойно пьет кофе и пишет код богов? Нет. Настоящий программист — это археолог, который раскапывает чужой код в 3 часа ночи и находит там регулярное выражение длиной в экран. Добро пожаловать в мир System.Text.RegularExpressions на C#. Место, где дружба заканчивается, а строки превращаются в пыль. Вначале ты думаешь: «Ну, проверить email? Легко! Пишем @"^[^@]+@[^@]+\.[^@]+$"». И оно работает! Ты гений. Ты пьёшь пиво и предвкушаешь, как сейчас всё автоматизируешь. А потом приходит задача: «Вытащить все номера телефонов из 500 файлов Word, включая формат "+7 (123) 456-78-90" и "8-800-555-35-35"». И вот тут начинается квест. В C# строки — это отдельный вид извращения. Если ты хочешь в регулярке найти обратную косую черту \, ты должен написать \\ в строке. Но так как это еще и спецсимвол регулярки, превращается это всё в \\\\. Смотришь на это: string pattern = "\\\\d{3}"; // Найти три цифры после слеша? А черт его знает. В голове у тебя н
Оглавление

Вы думаете, программист — это человек, который спокойно пьет кофе и пишет код богов? Нет. Настоящий программист — это археолог, который раскапывает чужой код в 3 часа ночи и находит там регулярное выражение длиной в экран.

Добро пожаловать в мир System.Text.RegularExpressions на C#. Место, где дружба заканчивается, а строки превращаются в пыль.

Этап 1: «Да это же просто, что тут сложного?»

Вначале ты думаешь: «Ну, проверить email? Легко! Пишем @"^[^@]+@[^@]+\.[^@]+$"».

И оно работает! Ты гений. Ты пьёшь пиво и предвкушаешь, как сейчас всё автоматизируешь.

А потом приходит задача: «Вытащить все номера телефонов из 500 файлов Word, включая формат "+7 (123) 456-78-90" и "8-800-555-35-35"».

И вот тут начинается квест.

-2

Этап 2: Боль и страдания под названием «Экранирование»

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

Смотришь на это:

string pattern = "\\\\d{3}"; // Найти три цифры после слеша? А черт его знает.

В голове у тебя начинает играть саундтрек из «Начала». Потому что это матрешка. И ты уже не понимаешь, где экранирование для компилятора C#, а где — для движка регулярных выражений.

Юмор дня: Чтобы написать точку (которая в регексе значит «любой символ»), нужно написать \.. А чтобы эта точка пролезла через C# строку, надо написать "\\.". Итого два символа превращаются в четыре. Профит!

-3

Этап 3: Класс Regex — твой новый враг

В C# есть Regex.IsMatch() — быстрый и злой. Есть Regex.Match() — возвращает один результат. И есть Regex.Matches() — коллекция.

И ты думаешь: «Ок, использую Matches».

Но нет! Если ты не обернешь это в using или не настроишь таймаут, твое приложение повиснет на строке в 500 килобайт текста. Почему? Потому что "catastrophic backtracking" — это не название хоррора на Netflix, это жизнь C# разработчика.

Пример смерти:

// Попробуй найди все 'a' в строке "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"
// с паттерном (a+)+b
// Компилятор: "Я подумаю. Подумаю еще раз. А давай все варианты переберу?"
// Результат: Ваш процессор плачет, а пользователь матерится.

-4

Этап 4: Тот самый коллега, который пишет «однострочники»

В каждом проекте есть человек, который вместо пяти if пишет:

var result = Regex.Replace(input, @"(?<=<tag>)(.*?)(?=</tag>)", m => m.Value.ToUpper());

Это выглядит как ругательство на эльфийском. Ты смотришь на (?<=...) (положительный просмотр назад) и (?=...) (просмотр вперед) и понимаешь: твой мозг только что сделал Stack Overflow.

Ирония: Через две недели даже автор этой строки не поймет, что она делает. Но удалять нельзя — сломается логика. Такая строка живет в коде дольше, чем средняя зарплата джуниора.

Этап 5: Магия RegexOptions

Самый прекрасный момент — когда ты забыл поставить RegexOptions.IgnoreCase и час ищешь, почему текст Hello не совпадает с hello.

А потом добавляешь RegexOptions.IgnorePatternWhitespace, чтобы в паттерне можно было писать комментарии. И твой код превращается в это:

string pattern = @"
^ # Начало строки
(?:[A-Za-z0-9]+ ) # Какое-то слово
$ # Конец строки
";

Выглядит красиво. Работает медленно. Но зато ты чувствуешь себя шаманом, который раскладывает руны.

Живой пример: проверяем, что пользователь не идиот

Допустим, мы пишем валидатор номера карты. Обычный вариант:

if (cardNumber.Length == 16 && cardNumber.All(char.IsDigit)) { ... }

Но мы же профи! Мы используем регулярки!

csharp

// \d{16} — 16 цифр
// (?!.*(\d)\1{3}) — проверка, что нет 4 одинаковых цифр подряд
string regex = @"^(?!.*(\d)\1{3})\d{16}$";

if (Regex.IsMatch(cardNumber, regex))
Console.WriteLine("Карта валидна (или нет, нам всё равно)");
else
Console.WriteLine("Вы ввели '1234 5678 9012 3456'? А пробелы я не умею. Идите лесом.");

Пользователь ввел номер с пробелами? Не наша проблема. Пусть учит матчасть.

Мораль (если она тут нужна)

Регулярные выражения в C# — это как острый перец. Чуть-чуть — отлично, раскрывает вкус блюда (кода). Много — вызовет изжогу (и баги). Слишком сложный паттерн — убьет прод.

Запомните три правила:

  1. Если вы написали регулярку и она сработала с первого раза — вам показалось. Проверьте еще раз.
  2. Для парсинга HTML регулярками сжигают на костре. Используйте AngleSharp или HtmlAgilityPack.
  3. Всегда задавайте TimeSpan в конструкторе Regex, если обрабатываете пользовательский ввод. Спасите свой CPU.

А какой самый странный баг из-за регулярок случался у вас? Расскажите в комментариях (только без спецсимволов, а то я начну их экранировать и уйду в запой).