Как сделать метод Contains(string) регистронезависимым в 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.