Avatar ·

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

Всем привет! Я начинающий C# разработчик, столкнулся с проблемой, которая кажется простой, но никак не могу найти правильное решение. У меня есть список строк, и я хочу проверить, содержит ли каждая строка определённое слово. Проблема в том, что метод Contains() по умолчанию чувствителен к регистру. Например, у меня есть код: ```csharp string text = "Привет, Мир!"; bool result = text.Contains("мир"); // возвращает false, хотя слово "Мир" есть ``` Я хочу, чтобы поиск работал независимо от того, в каком регистре написано искомое слово. Пробовал использовать ToLower() или ToUpper(), но это выглядит костыльно и некрасиво: ```csharp bool result = text.ToLower().Contains("мир".ToLower()); ``` К тому же слышал, что это может привести к проблемам с производительностью и с разными культурами (например, с турецкой буквой "i"). Подскажите, как правильно сделать регистронезависимый поиск в C#? Есть ли для этого встроенные методы? Заранее спасибо за помощь! <<>>

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

Начиная с .NET Framework 4.0 и .NET Core 2.0, у строк появилась перегрузка метода Contains, которая принимает параметр StringComparison. Это самый правильный и производительный способ сделать поиск регистронезависимым. Вот как это работает:

string text = "Привет, Мир!";
bool result = text.Contains("мир", StringComparison.OrdinalIgnoreCase);
Console.WriteLine(result); // Выведет: True

Почему это лучше, чем ToLower()?

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

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

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

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

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

Часто требуется проверить, содержит ли элемент списка подстроку без учёта регистра:

List<string> items = new List<string> 
{ 
    "C# Программирование", 
    "Python разработка", 
    "Java для начинающих" 
};

string searchTerm = "программирование";
var foundItems = items.Where(item => 
    item.Contains(searchTerm, StringComparison.OrdinalIgnoreCase));

foreach (var item in foundItems)
    Console.WriteLine(item); // Выведет: C# Программирование

Важное замечание

Если вы используете старую версию .NET Framework (до 4.0), то перегрузка с StringComparison недоступна. В таком случае можно написать свой метод-расширение:

public static class StringExtensions
{
    public static bool ContainsIgnoreCase(this string source, string value)
    {
        return source.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0;
    }
}

// Использование:
bool result = text.ContainsIgnoreCase("мир");

Метод IndexOf с StringComparison доступен во всех версиях .NET, поэтому это хорошая альтернатива для обратной совместимости.

Вывод

Всегда используйте StringComparison.OrdinalIgnoreCase для регистронезависимого поиска. Это быстро, безопасно и является современным стандартом в C#. Избегайте ToLower()/ToUpper() — это устаревший подход, который может привести к багам и проблемам с производительностью.

<<>>

Альтернативный подход: использование Regex с опцией IgnoreCase

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

using System.Text.RegularExpressions;

string text = "Привет, Мир!";
bool result = Regex.IsMatch(text, Regex.Escape("мир"), RegexOptions.IgnoreCase);
Console.WriteLine(result); // True

Плюсы:

  • Можно комбинировать с другими опциями: RegexOptions.IgnoreCase | RegexOptions.Multiline
  • Поддерживает сложные шаблоны поиска

Минусы:

  • Медленнее, чем Contains с StringComparison (особенно при массовых вызовах)
  • Требуется экранирование спецсимволов через Regex.Escape()
  • Избыточен для простого поиска подстроки

Этот подход стоит использовать только если вам действительно нужны возможности регулярных выражений (например, поиск с подстановочными знаками). Для обычного регистронезависимого поиска предпочтительнее первый вариант с StringComparison.OrdinalIgnoreCase.