Jak porównać ciągi w języku C#

Porównujesz ciągi, aby odpowiedzieć na jedno z dwóch pytań: "Czy te dwa ciągi są równe?" lub "W jakiej kolejności należy umieścić te ciągi podczas sortowania ich?"

Te dwa pytania są skomplikowane przez czynniki wpływające na porównania ciągów:

  • Możesz wybrać porównanie porządkowe lub językowe.
  • Możesz wybrać, czy sprawa ma znaczenie.
  • Możesz wybrać porównania specyficzne dla kultury.
  • Porównania językowe są zależne od kultury i platformy.

Pola System.StringComparison wyliczenia reprezentują następujące opcje:

  • CurrentCulture: porównaj ciągi przy użyciu reguł sortowania uwzględniającego kulturę i bieżącej kultury.
  • CurrentCultureIgnoreCase: porównuj ciągi przy użyciu reguł sortowania uwzględniającego kulturę, bieżącej kultury i ignorując przypadek porównywanych ciągów.
  • InvariantCulture: Porównuj ciągi przy użyciu reguł sortowania uwzględniającego kulturę i niezmiennej kultury.
  • InvariantCultureIgnoreCase: Porównuj ciągi przy użyciu reguł sortowania uwzględniających kulturę, niezmienną kulturę i ignorując przypadek porównywanych ciągów.
  • Porządkowe: porównywanie ciągów przy użyciu reguł sortowania porządkowego (binarnego).
  • OrdinalIgnoreCase: porównuje ciągi przy użyciu reguł sortowania porządkowego (binarnego) i ignorując przypadek porównywanych ciągów.

Uwaga

Przykłady języka C# w tym artykule są uruchamiane w Try.NET wbudowanym modułem uruchamiającym kod i placem zabaw. Wybierz przycisk Uruchom, aby uruchomić przykład w oknie interaktywnym. Po wykonaniu kodu można go zmodyfikować i uruchomić zmodyfikowany kod, wybierając pozycję Uruchom ponownie. Zmodyfikowany kod jest uruchamiany w oknie interaktywnym lub, jeśli kompilacja zakończy się niepowodzeniem, w oknie interaktywnym zostaną wyświetlone wszystkie komunikaty o błędach kompilatora języka C#.

Podczas porównywania ciągów należy zdefiniować kolejność między nimi. Porównania służą do sortowania sekwencji ciągów. Gdy sekwencja jest w znanej kolejności, łatwiej jest wyszukiwać zarówno oprogramowanie, jak i dla ludzi. Inne porównania mogą sprawdzić, czy ciągi są takie same. Te kontrole identyczności są podobne do równości, ale niektóre różnice, takie jak różnice wielkości liter, mogą być ignorowane.

Domyślne porównania porządkowe

Domyślnie najbardziej typowe operacje:

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")}");

Domyślne porównanie porządkowe nie uwzględnia reguł językowych podczas porównywania ciągów. Porównuje wartość binarną każdego Char obiektu w dwóch ciągach. W związku z tym domyślne porównanie porządkowe również uwzględnia wielkość liter.

Test równości z operatorami String.Equals i ==!= różni się od porównania ciągów przy użyciu String.CompareTo metod i .Compare(String, String) Wszystkie wykonują porównanie z uwzględnieniem wielkości liter. Jednak podczas gdy testy równości wykonują porównanie porządkowe, CompareTo metody i Compare wykonują porównanie językowe z uwzględnieniem kultury przy użyciu bieżącej kultury. Wyczyść intencję kodu, wywołując przeciążenie, które jawnie określa typ porównania do wykonania.

Porównania porządkowe bez uwzględniania wielkości liter

Metoda String.Equals(String, StringComparison) umożliwia określenie StringComparison wartości StringComparison.OrdinalIgnoreCase dla porównania porządkowego bez uwzględniania wielkości liter. Istnieje również metoda statyczna String.Compare(String, String, StringComparison) , która wykonuje porównanie porządkowe bez uwzględniania wielkości liter, jeśli określisz wartość StringComparison.OrdinalIgnoreCase argumentu StringComparison . Te porównania są wyświetlane w następującym kodzie:

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");

Te metody używają konwencji wielkości liter niezmiennej kultury podczas przeprowadzania porównania porządkowego bez uwzględniania wielkości liter.

Porównania językowe

Wiele metod porównania ciągów (takich jak String.StartsWith) domyślnie używa reguł językowych dla bieżącej kultury , aby uporządkować ich dane wejściowe. To porównanie językowe jest czasami określane jako "kolejność sortowania wyrazów". Podczas porównywania językowego niektóre niefanumeryczne znaki Unicode mogą mieć przypisane specjalne wagi. Na przykład łącznik "-" może mieć przypisaną niewielką wagę, tak aby obok siebie pojawiały się "co-op" i "coop" w kolejności sortowania. Niektóre znaki sterujące niedrukujące mogą być ignorowane. Ponadto niektóre znaki Unicode mogą być równoważne sekwencji Char wystąpień. W poniższym przykładzie użyto frazy "Tańczą na ulicy". w języku niemieckim z "ss" (U+0073 U+0073) w jednym ciągu i "ß" (U+00DF) w innym. Lingwistyczny (w systemie Windows), "ss" jest równy niemieckiemu znakowi Esszet: "ß" zarówno w kulturach "en-US" i "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");
}

W systemie Windows, przed .NET 5, kolejność sortowania "cop", "coop" i "co-op" zmienia się po zmianie z porównania językowego na porównanie porządkowe. Dwa zdania niemieckie również porównują się inaczej przy użyciu różnych typów porównania. Przed platformą .NET 5 interfejsy API globalizacji platformy .NET używały bibliotek obsługi języka narodowego (NLS ). W przypadku platformy .NET 5 i nowszych wersji interfejsy API globalizacji platformy .NET używają bibliotek International Components for Unicode (ICU), co łączy interfejs . Zachowanie globalizacji platformy NET we wszystkich obsługiwanych systemach operacyjnych.

Porównania korzystające z określonych kultur

W poniższym przykładzie są przechowywane CultureInfo obiekty dla kultur en-US i de-DE. Porównania są wykonywane przy użyciu CultureInfo obiektu w celu zapewnienia porównania specyficznego dla kultury. Używana kultura wpływa na porównania językowe. Poniższy przykład przedstawia wyniki porównywania dwóch zdań niemieckich przy użyciu kultury "en-US" i kultury "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");
}

Porównania wrażliwe na kulturę są zwykle używane do porównywania i sortowania ciągów wejściowych przez użytkowników z innymi ciągami wejściowymi przez użytkowników. Znaki i konwencje sortowania tych ciągów mogą się różnić w zależności od ustawień regionalnych komputera użytkownika. Nawet ciągi zawierające identyczne znaki mogą sortować inaczej w zależności od kultury bieżącego wątku.

Sortowanie językowe i wyszukiwanie ciągów w tablicach

W poniższych przykładach pokazano, jak sortować i wyszukiwać ciągi w tablicy przy użyciu porównania językowego zależnego od bieżącej kultury. Używasz metod statycznych Array , które przyjmują System.StringComparer parametr .

W poniższym przykładzie pokazano, jak sortować tablicę ciągów przy użyciu bieżącej kultury:

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}");
}

Po posortowania tablicy można wyszukać wpisy przy użyciu wyszukiwania binarnego. Wyszukiwanie binarne rozpoczyna się w środku kolekcji, aby określić, która połowa kolekcji będzie zawierać poszukiwany ciąg. Każde kolejne porównanie dzieli pozostałą część kolekcji w połowie. Tablica jest sortowana przy użyciu .StringComparer.CurrentCulture Funkcja ShowWhere lokalna wyświetla informacje o miejscu znalezienia ciągu. Jeśli ciąg nie został znaleziony, zwrócona wartość wskazuje, gdzie byłoby, gdyby został znaleziony.

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}.");
    }
}

Sortowanie porządkowe i wyszukiwanie w kolekcjach

Poniższy kod używa System.Collections.Generic.List<T> klasy kolekcji do przechowywania ciągów. Ciągi są sortowane przy użyciu List<T>.Sort metody . Ta metoda wymaga delegata, który porównuje i zamawia dwa ciągi. Metoda String.CompareTo zapewnia tę funkcję porównania. Uruchom przykład i obserwuj kolejność. Ta operacja sortowania używa sortowania z uwzględnieniem wielkości liter porządkowych. Metody statyczne String.Compare służą do określania różnych reguł porównania.

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}");
}

Po posortować listę ciągów można wyszukiwać przy użyciu wyszukiwania binarnego. W poniższym przykładzie pokazano, jak przeszukiwać posortowaną listę przy użyciu tej samej funkcji porównania. Funkcja ShowWhere lokalna pokazuje, gdzie znajduje się wyszukiwany tekst, lub może to być:

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}.");
    }
}

Zawsze pamiętaj, aby używać tego samego typu porównania do sortowania i wyszukiwania. Używanie różnych typów porównania do sortowania i wyszukiwania generuje nieoczekiwane wyniki.

Klasy kolekcji, takie jak , i mają konstruktory, które przyjmują System.StringComparer parametr, gdy typ elementów lub kluczy to string.System.Collections.Generic.List<T>System.Collections.Generic.Dictionary<TKey,TValue>System.Collections.Hashtable Ogólnie rzecz biorąc, należy użyć tych konstruktorów zawsze, gdy jest to możliwe, i określić wartość StringComparer.Ordinal lub StringComparer.OrdinalIgnoreCase.

Zobacz też