.Contains() без регистра в C#: 5 лучших способов в 2025

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему обычный .Contains() вас подведёт (и как это исправить)

Представьте: вы пишете поиск по каталогу книг на C#. Пользователь вводит «Толстой», а в базе хранится «толстой» с маленькой буквы. Обычный string.Contains("Толстой") вернёт false. Знакомая боль? Этот вопрос набрал тысячи просмотров на Stack Overflow — и не зря. В 2025 году, когда .NET предлагает мощные инструменты для работы со строками, использовать старый подход — значит терять пользователей и производительность.

В этой статье мы разберём 5 лучших способов сделать .Contains() регистронезависимым в C#. Вы узнаете, какой метод выбрать для максимальной скорости, а какой — для корректной работы с разными языками. Поехали!

Проблема: почему .Contains() по умолчанию чувствителен к регистру?

В C# метод string.Contains() по умолчанию использует Ordinal сравнение. Это значит, что символы сравниваются побайтово: 'A' (0x41) не равен 'a' (0x61). Для англоязычного контента это часто нормально, но в мультиязычных приложениях (русский, немецкий, турецкий) такой подход ломает логику поиска.

Например, в турецком языке буква 'i' в верхнем регистре — 'İ', а не 'I'. Обычное ToLower() может дать неверный результат. Поэтому просто вызывать text.ToLower().Contains(pattern.ToLower()) — плохая практика. Давайте посмотрим на современные решения.

Способ 1: StringComparison.OrdinalIgnoreCase — ваш новый стандарт

Начиная с .NET 5 (и отлично работает в .NET 8/9), самый быстрый и безопасный способ — использовать перегрузку Contains с параметром StringComparison:

string text = "Война и мир — Лев Толстой";
string searchTerm = "толстой";

bool result = text.Contains(searchTerm, StringComparison.OrdinalIgnoreCase);
Console.WriteLine(result); // True

Почему это лучший выбор?

  • Скорость: OrdinalIgnoreCase работает на уровне байтов, но игнорирует регистр. Это в 2-3 раза быстрее, чем CurrentCultureIgnoreCase.
  • Предсказуемость: Не зависит от культуры пользователя. "ß" (немецкая Eszett) не будет равна "ss", если вы этого не хотите.
  • Поддержка: Доступен во всех современных версиях .NET (Core 2.1+).

Используйте этот метод, когда вам нужно простое сравнение без учёта регистра, и вы не работаете с лингвистическими особенностями (сортировка, склонения).

Способ 2: IndexOf() с StringComparison — если нужно больше контроля

Иногда вам нужно не просто проверить наличие, но и узнать позицию вхождения. Или вы работаете с более старыми версиями .NET Framework, где Contains с StringComparison отсутствует. Тогда на помощь приходит IndexOf():

string text = "C# — лучший язык для backend";
string searchTerm = "Лучший";

bool result = text.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase) >= 0;
Console.WriteLine(result); // True

// А если нужно получить позицию:
int index = text.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase);
Console.WriteLine(index); // 4 (позиция, с которой начинается слово)

Когда использовать:

  • В legacy-проектах (например, .NET Framework 4.x).
  • Когда нужно найти индекс вхождения, а не просто проверить наличие.
  • Для более сложных сценариев (например, поиск с пропуском символов).

Важно: IndexOf возвращает -1, если подстрока не найдена. Всегда проверяйте на >= 0, а не на > 0.

Способ 3: CultureInfo.CurrentCultureIgnoreCase — для лингвистически корректного поиска

Если ваше приложение должно учитывать правила конкретного языка (например, немецкий умлаут или турецкая 'i'), используйте CurrentCultureIgnoreCase или InvariantCultureIgnoreCase:

using System.Globalization;

string text = "Straße"; // Немецкое слово "улица"
string searchTerm = "STRASSE";

// OrdinalIgnoreCase не сработает:
bool ordinalResult = text.Contains(searchTerm, StringComparison.OrdinalIgnoreCase);
Console.WriteLine(ordinalResult); // False (потому что ß != ss)

// А CurrentCultureIgnoreCase — сработает в немецкой культуре:
bool cultureResult = text.Contains(searchTerm, StringComparison.CurrentCultureIgnoreCase);
Console.WriteLine(cultureResult); // True (в культуре de-DE)

Минусы:

  • Медленнее: CurrentCultureIgnoreCase может быть в 5-10 раз медленнее OrdinalIgnoreCase.
  • Зависимость от культуры: Результат может меняться в зависимости от настроек сервера или пользователя.

Совет: Используйте InvariantCultureIgnoreCase как компромисс — он быстрее, чем CurrentCulture, и всё ещё учитывает некоторые лингвистические правила (например, для английского).

Способ 4: Регулярные выражения (Regex) — для сложных шаблонов

Если вам нужно не просто игнорировать регистр, но и искать по шаблону (например, "все слова, начинающиеся с 'толст'"), используйте Regex.IgnoreCase:

using System.Text.RegularExpressions;

string text = "Толстой написал 'Войну и мир'. Толстые книги — это здорово.";
string pattern = @"\bтолст\w*"; // Слова, начинающиеся с "толст"

bool result = Regex.IsMatch(text, pattern, RegexOptions.IgnoreCase);
Console.WriteLine(result); // True (найдёт "Толстой", "толстые")

// А если нужно получить все совпадения:
MatchCollection matches = Regex.Matches(text, pattern, RegexOptions.IgnoreCase);
foreach (Match match in matches)
{
    Console.WriteLine(match.Value); // Выведет: "Толстой", "Толстые"
}

Когда нужно:

  • Поиск по сложным шаблонам (регулярные выражения).
  • Поиск с игнорированием регистра + другие опции (многострочный режим и т.д.).

Внимание: Regex медленнее обычных строковых методов. Для простого поиска всегда предпочитайте Contains с StringComparison.

Способ 5: Кастомный экстеншн-метод (для старых .NET Framework)

Если вы застряли на .NET Framework 4.6.2 или ниже, где нет перегрузки Contains с StringComparison, напишите свой extension-метод

Блоги

Рекомендаций по книгам