Сравнение строк в C#

Вы сравниваете строки, чтобы ответить на один из двух вопросов: "Равны ли эти две строки?" или "В каком порядке следует размещать эти строки при их сортировке?"

Однако эту задачу осложняют факторы, влияющие на сравнение строк:

  • Вы можете выбрать порядковое или лингвистическое сравнение.
  • Вы можете указать, учитывается ли регистр.
  • Вы можете выбрать сравнения для конкретного языка и региональных параметров.
  • Лингвистические сравнения зависят от языка и региональных параметров, а также от используемой платформы.

Примечание

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

При сравнении строк вы определяете их порядок. Сравнения используются для сортировки последовательности строк. Если последовательность имеет известный порядок, это упрощает поиск как для программного обеспечения, так и для пользователей. Другие сравнения могут проверять совпадение строк. Эти проверки тождественности похожи на проверки равенства, но позволяют игнорировать некоторые различия, например различия в регистре.

Порядковые сравнения по умолчанию

Ниже представлены самые распространенные операции по умолчанию:

выполнить сравнение порядкового номера с учетом регистра. В случае String.Equalsможно указать аргумент для StringComparison изменения правил сортировки. Это показано в следующем примере:

string root = @"C:\users";
string root2 = @"C:\Users";

bool result = root.Equals(root2);
Console.WriteLine($"Ordinal comparison: <{root}> and <{root2}> are {(result ? "equal." : "not equal.")}");

result = root.Equals(root2, StringComparison.Ordinal);
Console.WriteLine($"Ordinal comparison: <{root}> and <{root2}> are {(result ? "equal." : "not equal.")}");

Console.WriteLine($"Using == says that <{root}> and <{root2}> are {(root == root2 ? "equal" : "not equal")}");

При порядковых сравнениях строк по умолчанию лингвистические правила не учитываются. В них сравнивается двоичное значение каждого объекта Char в двух строках. Таким образом, в порядковом сравнении по умолчанию также учитывается регистр.

Проверка на равенство с использованием String.Equals, а также операторов == и != отличается от сравнения строк с использованием методов String.CompareTo и Compare(String, String). Все они выполняют сравнение с учетом регистра. Однако в то время как тесты на равенство выполняют порядковые сравнения, CompareTo методы и выполняют лингвистическое сравнение с учетом языка и Compare региональных параметров с использованием текущего языка и региональных параметров. Так как эти методы сравнения по умолчанию отличаются способами сравнения строк, рекомендуется всегда четко указывать намерение кода, вызывая перегрузку, которая явно указывает тип выполняемого сравнения.

Порядковые сравнения без учета регистра

Метод String.Equals(String, StringComparison) позволяет указать StringComparison значение StringComparison.OrdinalIgnoreCase для порядкового сравнения без учета регистра. Также имеется статический метод String.Compare(String, String, StringComparison), позволяющий проводить порядковое сравнение без учета регистра, если указать значение StringComparison.OrdinalIgnoreCase для аргумента StringComparison. Это показано в следующем коде:

string root = @"C:\users";
string root2 = @"C:\Users";

bool result = root.Equals(root2, StringComparison.OrdinalIgnoreCase);
bool areEqual = String.Equals(root, root2, StringComparison.OrdinalIgnoreCase);
int comparison = String.Compare(root, root2, comparisonType: StringComparison.OrdinalIgnoreCase);

Console.WriteLine($"Ordinal ignore case: <{root}> and <{root2}> are {(result ? "equal." : "not equal.")}");
Console.WriteLine($"Ordinal static ignore case: <{root}> and <{root2}> are {(areEqual ? "equal." : "not equal.")}");
if (comparison < 0)
    Console.WriteLine($"<{root}> is less than <{root2}>");
else if (comparison > 0)
    Console.WriteLine($"<{root}> is greater than <{root2}>");
else
    Console.WriteLine($"<{root}> and <{root2}> are equivalent in order");

При выполнении порядкового сравнения без учета регистра эти методы используют соглашения о регистре для инвариантного языка и региональных параметров.

Лингвистические сравнения

Многие методы сравнения строк (например, String.StartsWith) используют лингвистические правила для текущего языка и региональных параметров по умолчанию, чтобы упорядочить входные данные. Это иногда называют "порядком сортировки слов". При выполнении лингвистического сравнения некоторым символам Юникода, не имеющим параметров, могут быть назначены специальные весовые коэффициенты. Например, дефису "-" может быть присвоен небольшой вес, чтобы "co-op" и "coop" отображались рядом друг с другом в порядке сортировки, в то время как некоторые непечатаемые управляющие символы могут быть полностью проигнорированы. Кроме того, некоторые символы Юникода могут быть эквивалентны последовательности экземпляров Char. В следующем примере используется фраза "Они танцуют на улице"." на немецком языке с "ss" (U+0073 U+0073) в одной строке и "ß" (U+00DF) в другой. Лингвистически (в Windows) буквы "ss" равнозначны немецкому символу эсцет "ß" в языках "en-US" и "de-DE".

string first = "Sie tanzen auf der Straße.";
string second = "Sie tanzen auf der Strasse.";

Console.WriteLine($"First sentence is <{first}>");
Console.WriteLine($"Second sentence is <{second}>");

bool equal = String.Equals(first, second, StringComparison.InvariantCulture);
Console.WriteLine($"The two strings {(equal == true ? "are" : "are not")} equal.");
showComparison(first, second);

string word = "coop";
string words = "co-op";
string other = "cop";

showComparison(word, words);
showComparison(word, other);
showComparison(words, other);
void showComparison(string one, string two)
{
    int compareLinguistic = String.Compare(one, two, StringComparison.InvariantCulture);
    int compareOrdinal = String.Compare(one, two, StringComparison.Ordinal);
    if (compareLinguistic < 0)
        Console.WriteLine($"<{one}> is less than <{two}> using invariant culture");
    else if (compareLinguistic > 0)
        Console.WriteLine($"<{one}> is greater than <{two}> using invariant culture");
    else
        Console.WriteLine($"<{one}> and <{two}> are equivalent in order using invariant culture");
    if (compareOrdinal < 0)
        Console.WriteLine($"<{one}> is less than <{two}> using ordinal comparison");
    else if (compareOrdinal > 0)
        Console.WriteLine($"<{one}> is greater than <{two}> using ordinal comparison");
    else
        Console.WriteLine($"<{one}> and <{two}> are equivalent in order using ordinal comparison");
}

В Windows до .NET 5 порядок сортировки "cop", "coop" и "co-op" изменяется при переходе с лингвистического сравнения на порядковый номер. Два предложения на немецком языке также сравниваются по-разному при использовании разных типов сравнения. Это связано с тем, что до .NET 5 API глобализации .NET использовали библиотеки поддержки национальных языков (NLS ). В .NET 5 и более поздних версиях API глобализации .NET используют международные компоненты для библиотек Юникода (ICU ), которые объединяют . Поведение NET по глобализации во всех поддерживаемых операционных системах.

Сравнения с использованием определенных языков и региональных параметров

Этот пример сохраняет объекты CultureInfo для языков "en-US" и "de-DE". Сравнения выполняются с использованием объекта CultureInfo, чтобы учесть язык и региональные параметры.

Используемые значения языка и региональных параметров влияют на операции лингвистического сравнения. В следующем примере показаны результаты сравнения двух предложений на немецком с использованием языка и региональных параметров "en US" и "de-DE":

string first = "Sie tanzen auf der Straße.";
string second = "Sie tanzen auf der Strasse.";

Console.WriteLine($"First sentence is <{first}>");
Console.WriteLine($"Second sentence is <{second}>");

var en = new System.Globalization.CultureInfo("en-US");

// For culture-sensitive comparisons, use the String.Compare
// overload that takes a StringComparison value.
int i = String.Compare(first, second, en, System.Globalization.CompareOptions.None);
Console.WriteLine($"Comparing in {en.Name} returns {i}.");

var de = new System.Globalization.CultureInfo("de-DE");
i = String.Compare(first, second, de, System.Globalization.CompareOptions.None);
Console.WriteLine($"Comparing in {de.Name} returns {i}.");

bool b = String.Equals(first, second, StringComparison.CurrentCulture);
Console.WriteLine($"The two strings {(b ? "are" : "are not")} equal.");

string word = "coop";
string words = "co-op";
string other = "cop";

showComparison(word, words, en);
showComparison(word, other, en);
showComparison(words, other, en);
void showComparison(string one, string two, System.Globalization.CultureInfo culture)
{
    int compareLinguistic = String.Compare(one, two, en, System.Globalization.CompareOptions.None);
    int compareOrdinal = String.Compare(one, two, StringComparison.Ordinal);
    if (compareLinguistic < 0)
        Console.WriteLine($"<{one}> is less than <{two}> using en-US culture");
    else if (compareLinguistic > 0)
        Console.WriteLine($"<{one}> is greater than <{two}> using en-US culture");
    else
        Console.WriteLine($"<{one}> and <{two}> are equivalent in order using en-US culture");
    if (compareOrdinal < 0)
        Console.WriteLine($"<{one}> is less than <{two}> using ordinal comparison");
    else if (compareOrdinal > 0)
        Console.WriteLine($"<{one}> is greater than <{two}> using ordinal comparison");
    else
        Console.WriteLine($"<{one}> and <{two}> are equivalent in order using ordinal comparison");
}

Сравнения с учетом языка и региональных параметров обычно используются для сравнения и сортировки строк, вводимых пользователями. Символы и правила сортировки этих строк могут различаться в зависимости от языкового стандарта компьютера пользователя. Даже строки, содержащие идентичные символы, могут быть отсортированы по-разному, в зависимости от языка и региональных параметров текущего потока.

Лингвистическая сортировка и поиск строк в массивах

Приведенные ниже примеры показывают, как сортировать и искать строки в массиве с помощью лингвистического сравнения, зависящего от текущих значений языка и региональных параметров. Используйте статические методы Array, которые принимают параметр System.StringComparer.

В этом примере показано, как сортировать массив строк с использованием текущих значений языка и региональных параметров:

string[] lines = new string[]
{
    @"c:\public\textfile.txt",
    @"c:\public\textFile.TXT",
    @"c:\public\Text.txt",
    @"c:\public\testfile2.txt"
};

Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
    Console.WriteLine($"   {s}");
}

Console.WriteLine("\n\rSorted order:");

// Specify Ordinal to demonstrate the different behavior.
Array.Sort(lines, StringComparer.CurrentCulture);

foreach (string s in lines)
{
    Console.WriteLine($"   {s}");
}

После сортировки массива можно выполнить поиск записей с помощью двоичного поиска. Двоичный поиск начинается с середины коллекции, чтобы определить, какая половина коллекции содержит искомую строку. Каждое последующее сравнение делит оставшуюся часть коллекции пополам. Массив сортируется с использованием StringComparer.CurrentCulture. Локальная функция ShowWhere отображает сведения о том, где была найдена строка. Если строка не найдена, возвращаемое значение указывает, где бы оно находилось, если было бы найдено.

string[] lines = new string[]
{
    @"c:\public\textfile.txt",
    @"c:\public\textFile.TXT",
    @"c:\public\Text.txt",
    @"c:\public\testfile2.txt"
};
Array.Sort(lines, StringComparer.CurrentCulture);

string searchString = @"c:\public\TEXTFILE.TXT";
Console.WriteLine($"Binary search for <{searchString}>");
int result = Array.BinarySearch(lines, searchString, StringComparer.CurrentCulture);
ShowWhere<string>(lines, result);

Console.WriteLine($"{(result > 0 ? "Found" : "Did not find")} {searchString}");

void ShowWhere<T>(T[] array, int index)
{
    if (index < 0)
    {
        index = ~index;

        Console.Write("Not found. Sorts between: ");

        if (index == 0)
            Console.Write("beginning of sequence and ");
        else
            Console.Write($"{array[index - 1]} and ");

        if (index == array.Length)
            Console.WriteLine("end of sequence.");
        else
            Console.WriteLine($"{array[index]}.");
    }
    else
    {
        Console.WriteLine($"Found at index {index}.");
    }
}

Порядковая сортировка и поиск в коллекциях

Следующий код использует класс коллекции System.Collections.Generic.List<T> для хранения строк. Строки сортируются с помощью метода List<T>.Sort. Этому методу нужен делегат, который сравнивает и упорядочивает две строки. Метод String.CompareTo предоставляет эту функцию сравнения. Запустите пример и следите за порядком. Эта операция сортировки использует порядковую сортировку с учетом регистра. Можно использовать статические методы String.Compare, чтобы указать разные правила сравнения.

List<string> lines = new List<string>
{
    @"c:\public\textfile.txt",
    @"c:\public\textFile.TXT",
    @"c:\public\Text.txt",
    @"c:\public\testfile2.txt"
};

Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
    Console.WriteLine($"   {s}");
}

Console.WriteLine("\n\rSorted order:");

lines.Sort((left, right) => left.CompareTo(right));
foreach (string s in lines)
{
    Console.WriteLine($"   {s}");
}

После сортировки по списку строк можно осуществлять двоичный поиск. В приведенном ниже примере показан поиск в отсортированном списке с использованием той же функции сравнения. Локальная функция ShowWhere показывает, где находится или находился бы искомый текст:

List<string> lines = new List<string>
{
    @"c:\public\textfile.txt",
    @"c:\public\textFile.TXT",
    @"c:\public\Text.txt",
    @"c:\public\testfile2.txt"
};
lines.Sort((left, right) => left.CompareTo(right));

string searchString = @"c:\public\TEXTFILE.TXT";
Console.WriteLine($"Binary search for <{searchString}>");
int result = lines.BinarySearch(searchString);
ShowWhere<string>(lines, result);

Console.WriteLine($"{(result > 0 ? "Found" : "Did not find")} {searchString}");

void ShowWhere<T>(IList<T> collection, int index)
{
    if (index < 0)
    {
        index = ~index;

        Console.Write("Not found. Sorts between: ");

        if (index == 0)
            Console.Write("beginning of sequence and ");
        else
            Console.Write($"{collection[index - 1]} and ");

        if (index == collection.Count)
            Console.WriteLine("end of sequence.");
        else
            Console.WriteLine($"{collection[index]}.");
    }
    else
    {
        Console.WriteLine($"Found at index {index}.");
    }
}

Всегда используйте один и тот же тип сравнения для сортировки и поиска. Использование разных типов сравнения приводит к неожиданным результатам.

Классы коллекций, такие как System.Collections.Hashtable, System.Collections.Generic.Dictionary<TKey,TValue>, и System.Collections.Generic.List<T>, имеют конструкторы, принимающие параметр System.StringComparer, если типом элементов или ключей является string. В целом, по возможности следует использовать эти конструкторы и задавать либо StringComparer.Ordinal, либо StringComparer.OrdinalIgnoreCase.

См. также