Avatar ·

Как сделать метод Contains(string) регистронезависимым в C#?

Всем привет! Я начинающий разработчик на C# и столкнулся с проблемой, которая кажется простой, но никак не могу найти правильное решение. У меня есть список строк, и я хочу проверить, содержит ли каждая строка определённое слово. Проблема в том, что регистр может быть разным. Например, у меня есть строка "Hello World", и я ищу "world" (с маленькой буквы). По умолчанию метод Contains возвращает false, потому что он чувствителен к регистру. Вот пример моего кода: ```csharp string text = "Привет, Мир!"; string searchTerm = "мир"; if (text.Contains(searchTerm)) { Console.WriteLine("Найдено!"); } else { Console.WriteLine("Не найдено."); } ``` В этом случае "мир" не будет найден, хотя в строке есть "Мир" с большой буквы. Я пробовал использовать ToLower() для обеих строк, но это кажется костылём, и мне сказали, что это неэффективно для больших объёмов данных. Кроме того, это может привести к проблемам с интернационализацией (например, с турецкой буквой "i"). Подскажите, пожалуйста, как правильно сделать поиск регистронезависимым в C#? Есть ли встроенные методы для этого? Желательно с примером кода. Заранее спасибо! <<>>

Использование StringComparison с перегрузкой Contains

В современном C# (начиная с .NET Core 2.1 / .NET 5+) у метода string.Contains появилась перегрузка, которая принимает параметр StringComparison. Это самый правильный и производительный способ сделать поиск регистронезависимым.

string text = "Привет, Мир!";
string searchTerm = "мир";

bool result = text.Contains(searchTerm, StringComparison.OrdinalIgnoreCase);

if (result)
{
    Console.WriteLine("Найдено!");
}
else
{
    Console.WriteLine("Не найдено.");
}
// Вывод: Найдено!

Почему это лучший подход?

  • Производительность: Не создаются временные строки (как при использовании ToLower()), что экономит память и время.
  • Культурная корректность: StringComparison.OrdinalIgnoreCase работает на основе порядкового сравнения без учёта регистра. Для большинства сценариев поиска подстрок это оптимально.
  • Читаемость: Код явно указывает намерение разработчика.

Другие варианты StringComparison

В зависимости от задачи можно использовать разные значения:

  • StringComparison.OrdinalIgnoreCase — для технического поиска (пароли, коды, идентификаторы). Самый быстрый.
  • StringComparison.CurrentCultureIgnoreCase — для текста, ориентированного на текущую культуру пользователя.
  • StringComparison.InvariantCultureIgnoreCase — для данных, которые должны обрабатываться одинаково во всех культурах (например, для сортировки в интерфейсе).

Что делать, если у вас старая версия .NET Framework?

Если вы застряли на .NET Framework 4.x или ниже, где нет перегрузки Contains с StringComparison, используйте метод IndexOf:

string text = "Привет, Мир!";
string searchTerm = "мир";

bool result = text.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase) >= 0;

if (result)
{
    Console.WriteLine("Найдено!");
}

IndexOf возвращает индекс первого вхождения подстроки или -1, если ничего не найдено. Этот метод доступен во всех версиях .NET Framework.

Почему не стоит использовать ToLower()?

Многие новички делают так:

bool result = text.ToLower().Contains(searchTerm.ToLower());

Этот подход плох по нескольким причинам:

  • Создаются две новые строки в памяти (для text.ToLower() и searchTerm.ToLower()).
  • Если строки большие или поиск выполняется в цикле, это приводит к лишней аллокации и сборке мусора.
  • Проблемы с интернационализацией: например, в турецком языке буквы "i" и "I" соотносятся иначе, чем в английском.

Пример для списка строк

List<string> items = new List<string> { "Яблоко", "Банан", "Вишня" };
string search = "банан";

// Используем LINQ с Contains
var found = items.Where(item => item.Contains(search, StringComparison.OrdinalIgnoreCase));

foreach (var item in found)
{
    Console.WriteLine(item); // Выведет: Банан
}

Используйте StringComparison.OrdinalIgnoreCase как стандартный выбор для регистронезависимого поиска — это современно, быстро и правильно.

<<>>

Использование Regex с опцией IgnoreCase

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

using System.Text.RegularExpressions;

string text = "Привет, Мир!";
string searchTerm = "мир";

bool result = Regex.IsMatch(text, searchTerm, RegexOptions.IgnoreCase);

Console.WriteLine(result); // True

Когда это полезно?

  • Когда нужно искать не просто подстроку, а целое слово (используйте \b): @"\b" + searchTerm + @"\b".
  • Когда нужно заменить все вхождения без учёта регистра.
  • Для сложных текстовых шаблонов (например, поиск email или номера телефона).

Недостатки

  • Медленнее, чем Contains с StringComparison, особенно при многократном вызове.
  • Требуется экранирование специальных символов (если searchTerm введён пользователем, используйте Regex.Escape).
string userInput = "мир.";
string escaped = Regex.Escape(userInput); // "мир\\."
bool result = Regex.IsMatch(text, escaped, RegexOptions.IgnoreCase);

Используйте Regex только тогда, когда вам действительно нужны возможности регулярных выражений. Для простого регистронезависимого поиска лучше подойдёт первый вариант с StringComparison.