Osvědčené postupy pro porovnávání řetězců v .NET

.NET poskytuje rozsáhlou podporu pro vývoj lokalizovaných a globalizovaných aplikací a usnadňuje použití konvencí aktuální jazykové verze nebo konkrétní jazykové verze při provádění běžných operací, jako je řazení a zobrazování řetězců. Řazení nebo porovnávání řetězců ale není vždy operace citlivá na jazykovou verzi. Například řetězce, které aplikace používá interně, by se obvykle měly zpracovávat stejně napříč všemi jazykovými verzemi. Pokud jsou data řetězců nezávislá na kultuře, jako jsou značky XML, značky HTML, uživatelská jména, cesty k souborům a názvy systémových objektů, interpretována, jako by se jednalo o jazykovou verzi, kód aplikace může podléhat drobným chybám, špatnému výkonu a v některých případech problémům se zabezpečením.

Tento článek zkoumá metody řazení, porovnávání a velikostí písmen v .NET, obsahuje doporučení pro výběr vhodné metody zpracování řetězců a poskytuje další informace o metodách zpracování řetězců.

Doporučení pro použití řetězců

Při vývoji pomocí .NET při porovnávání řetězců postupujte podle těchto doporučení.

Tip

Porovnání provádí různé metody související s řetězci. Mezi příklady patří String.Equals, String.Compare, String.IndexOf, a String.StartsWith.

Při porovnávání řetězců se vyhněte následujícím postupům:

  • Nepoužívejte přetížení, která explicitně ani implicitně nezadávají pravidla porovnání řetězců pro řetězcové operace.
  • Ve většině případů nepoužívejte řetězcové operace StringComparison.InvariantCulture . Jednou z několika výjimek je, když budete uchovávat lingvisticky smysluplná, ale kulturně nezávislá data.
  • Nepoužívejte přetížení nebo CompareTo metodu String.Compare a otestujte návratovou hodnotu nuly, abyste zjistili, zda jsou dva řetězce stejné.

Explicitní zadání porovnání řetězců

Většina metod manipulace s řetězci v .NET je přetížená. Jedno nebo více přetížení obvykle přijímají výchozí nastavení, zatímco ostatní přijímají žádné výchozí hodnoty a místo toho definují přesný způsob, jakým se mají řetězce porovnávat nebo manipulovat. Většina metod, které nespoléhá na výchozí hodnoty, zahrnují parametr typu StringComparison, což je výčet, který explicitně určuje pravidla pro porovnání řetězců podle jazykové verze a případu. Následující tabulka popisuje členy výčtu StringComparison .

StringComparison – člen Popis
CurrentCulture Provede porovnání s rozlišováním velkých a malých písmen pomocí aktuální jazykové verze.
CurrentCultureIgnoreCase Provede porovnání nerozlišující malá a velká písmena pomocí aktuální jazykové verze.
InvariantCulture Provede porovnání s rozlišováním velkých a malých písmen pomocí neutrální jazykové verze.
InvariantCultureIgnoreCase Provede porovnání bez rozlišování velkých a malých písmen pomocí invariantní jazykové verze.
Ordinal Provede řadové porovnání.
OrdinalIgnoreCase Provádí porovnávání bez rozlišování malých a velkých písmen.

Například IndexOf metoda, která vrací index podřetězce v objektu String , který odpovídá znaku nebo řetězci, má devět přetížení:

Doporučujeme vybrat přetížení, které nepoužívá výchozí hodnoty, z následujících důvodů:

  • Některá přetížení s výchozími parametry (ty, které hledají Char v instanci řetězce) provádějí řadové porovnání, zatímco jiné (ty, které hledají řetězec v instanci řetězce) jsou citlivé na jazykovou verzi. Je obtížné si zapamatovat, která metoda používá, která výchozí hodnota, a snadno zaměňovat přetížení.

  • Záměr kódu, který spoléhá na výchozí hodnoty volání metody, není jasný. V následujícím příkladu, který spoléhá na výchozí hodnoty, je obtížné zjistit, jestli vývojář skutečně zamýšlel pořadové nebo lingvistické porovnání dvou řetězců, nebo jestli rozdíl url.Scheme mezi písmeny a "https" může způsobit, že test rovnosti vrátí false.

    Uri url = new("https://learn.microsoft.com/");
    
    // Incorrect
    if (string.Equals(url.Scheme, "https"))
    {
        // ...Code to handle HTTPS protocol.
    }
    
    Dim url As New Uri("https://learn.microsoft.com/")
    
    ' Incorrect
    If String.Equals(url.Scheme, "https") Then
        ' ...Code to handle HTTPS protocol.
    End If
    

Obecně doporučujeme volat metodu, která nespoléhá na výchozí hodnoty, protože je záměr kódu jednoznačný. Díky tomu je kód čitelnější a snadněji se ladí a udržuje. Následující příklad řeší otázky vyvolané v předchozím příkladu. Je zřejmé, že se používá řadové porovnání a že rozdíly v případě ignorovány.

Uri url = new("https://learn.microsoft.com/");

// Correct
if (string.Equals(url.Scheme, "https", StringComparison.OrdinalIgnoreCase))
{
    // ...Code to handle HTTPS protocol.
}
Dim url As New Uri("https://learn.microsoft.com/")

' Incorrect
If String.Equals(url.Scheme, "https", StringComparison.OrdinalIgnoreCase) Then
    ' ...Code to handle HTTPS protocol.
End If

Podrobnosti o porovnání řetězců

Porovnání řetězců je jádrem mnoha operací souvisejících s řetězci, zejména řazení a testování rovnosti. Řetězce se seřadí v určeném pořadí: Pokud se "my" zobrazí před "řetězec" v seřazených návazcích, musí "my" porovnat menší nebo rovno "řetězec". Porovnání navíc implicitně definuje rovnost. Operace porovnání vrátí nula pro řetězce, které považuje za rovno. Dobrou interpretací je, že ani jeden řetězec není menší než druhý. Nejvýstižnější operace zahrnující řetězce zahrnují jeden nebo oba tyto postupy: porovnávání s jiným řetězcem a provádění dobře definované operace řazení.

Poznámka:

Můžete si stáhnout tabulky hmotnosti řazení, sadu textových souborů, které obsahují informace o váhách znaků používaných při řazení a porovnání operací pro operační systémy Windows, a tabulku výchozích prvků kolace Unicode, nejnovější verzi tabulky hmotnosti řazení pro Linux a macOS. Konkrétní verze tabulky hmotnosti řazení v Systému Linux a macOS závisí na verzi knihoven International Components for Unicode nainstalovaných v systému. Informace o verzích ICU a verzích Unicode, které implementují, najdete v tématu Stažení ICU.

Vyhodnocení dvou řetězců pro rovnost nebo pořadí řazení však nepřináší jediný, správný výsledek; výsledek závisí na kritériích použitých k porovnání řetězců. Konkrétně porovnání řetězců, která jsou řadová nebo která jsou založená na konvencích dělení a řazení aktuální jazykové verze nebo invariantní jazykové verze (jazyková verze nezávislá na národním prostředí na základě anglického jazyka), mohou vést k různým výsledkům.

Kromě toho porovnání řetězců s různými verzemi .NET nebo .NET v různých operačních systémech nebo verzích operačního systému můžou vracet různé výsledky. Další informace naleznete v tématu Řetězce a Standard Unicode.

Porovnání řetězců, která používají aktuální jazykovou verzi

Jedním z kritérií je použití konvencí aktuální jazykové verze při porovnávání řetězců. Porovnání založená na aktuální jazykové verzi používají aktuální jazykovou verzi nebo národní prostředí vlákna. Pokud uživatel jazykovou verzi nenastaví, nastaví se jako výchozí nastavení operačního systému. Vždy byste měli použít porovnání založená na aktuální jazykové verzi, pokud jsou data lingvisticky relevantní, a když odráží interakci uživatele citlivé na jazykovou verzi.

Při změně jazykové verze se však změní chování porovnání a velikosti v .NET. K tomu dochází, když se aplikace spustí v počítači, který má jinou jazykovou verzi než počítač, na kterém byla aplikace vyvinuta, nebo když provádění vlákna změní jeho jazykovou verzi. Toto chování je záměrné, ale pro mnoho vývojářů to není zřejmé. Následující příklad znázorňuje rozdíly v pořadí řazení mezi jazykovými verzemi USA ("en-US") a švédštiny ("sv-SE"). Všimněte si, že slova "ångström", "Windows" a "Visual Studio" se zobrazují v různých pozicích v maticích seřazených řetězců.

using System.Globalization;

// Words to sort
string[] values= { "able", "ångström", "apple", "Æble",
                    "Windows", "Visual Studio" };

// Current culture
Array.Sort(values);
DisplayArray(values);

// Change culture to Swedish (Sweden)
string originalCulture = CultureInfo.CurrentCulture.Name;
Thread.CurrentThread.CurrentCulture = new CultureInfo("sv-SE");
Array.Sort(values);
DisplayArray(values);

// Restore the original culture
Thread.CurrentThread.CurrentCulture = new CultureInfo(originalCulture);

static void DisplayArray(string[] values)
{
    Console.WriteLine($"Sorting using the {CultureInfo.CurrentCulture.Name} culture:");
    
    foreach (string value in values)
        Console.WriteLine($"   {value}");

    Console.WriteLine();
}

// The example displays the following output:
//     Sorting using the en-US culture:
//        able
//        Æble
//        ångström
//        apple
//        Visual Studio
//        Windows
//
//     Sorting using the sv-SE culture:
//        able
//        apple
//        Visual Studio
//        Windows
//        ångström
//        Æble
Imports System.Globalization
Imports System.Threading

Module Program
    Sub Main()
        ' Words to sort
        Dim values As String() = {"able", "ångström", "apple", "Æble",
                                  "Windows", "Visual Studio"}

        ' Current culture
        Array.Sort(values)
        DisplayArray(values)

        ' Change culture to Swedish (Sweden)
        Dim originalCulture As String = CultureInfo.CurrentCulture.Name
        Thread.CurrentThread.CurrentCulture = New CultureInfo("sv-SE")
        Array.Sort(values)
        DisplayArray(values)

        ' Restore the original culture
        Thread.CurrentThread.CurrentCulture = New CultureInfo(originalCulture)
    End Sub

    Sub DisplayArray(values As String())
        Console.WriteLine($"Sorting using the {CultureInfo.CurrentCulture.Name} culture:")

        For Each value As String In values
            Console.WriteLine($"   {value}")
        Next

        Console.WriteLine()
    End Sub
End Module

' The example displays the following output:
'     Sorting using the en-US culture:
'        able
'        Æble
'        ångström
'        apple
'        Visual Studio
'        Windows
'
'     Sorting using the sv-SE culture:
'        able
'        apple
'        Visual Studio
'        Windows
'        ångström
'        Æble

Porovnání nerozlišující malá a velká písmena, která používají aktuální jazykovou verzi, jsou stejná jako porovnání citlivá na jazykovou verzi, s tím rozdílem, že ignorují malá a velká písmena podle aktuální jazykové verze vlákna. Toto chování se může projevit také v pořadí řazení.

Porovnání, která používají sémantiku aktuální jazykové verze, jsou výchozí pro následující metody:

V každém případě doporučujeme volat přetížení, které má StringComparison parametr, aby byl záměr volání metody jasný.

Drobné a ne tak drobné chyby se mohou objevit, když se jazykově interpretují nejazyčná řetězcová data nebo když se řetězcová data z konkrétní jazykové verze interpretují pomocí konvencí jiné jazykové verze. Kanonický příklad je turecko-I problém.

Pro téměř všechny latinky, včetně angličtiny v USA, je znak "i" (\u0069) malými písmeny znaku "I" (\u0049). Toto pravidlo casingu se rychle stane výchozím nastavením pro někoho, kdo programuje v takové jazykové verzi. Abeceda turečtina ("tr-TR") však obsahuje znak "I s tečkou" (\u0130), což je hlavní verze "i". Turečtina obsahuje také malá písmena "i bez tečky", "ı" (\u0131), která má velká písmena na "I". K tomuto chování dochází také v ázerbájdžánské kultuře ("az").

Proto předpoklady týkající se velkých písmen "i" nebo nižších písmen "I" nejsou platné mezi všemi kulturami. Pokud použijete výchozí přetížení pro rutiny porovnání řetězců, budou podléhat rozptylu mezi jazykovými verzemi. Pokud porovnávaná data nejsou jazyková, může použití výchozích přetížení vést k nežádoucím výsledkům, protože následující pokus o provedení porovnání řetězců bill a BILL nerozlišuje malá a velká písmena.

using System.Globalization;

string name = "Bill";

Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}");
Console.WriteLine($"   Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}");
Console.WriteLine($"   Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", true, null)}");
Console.WriteLine();

Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}");
Console.WriteLine($"   Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}");
Console.WriteLine($"   Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", true, null)}");

//' The example displays the following output:
//'
//'     Culture = English (United States)
//'        Is 'Bill' the same as 'BILL'? True
//'        Does 'Bill' start with 'BILL'? True
//'     
//'     Culture = Turkish (Türkiye)
//'        Is 'Bill' the same as 'BILL'? True
//'        Does 'Bill' start with 'BILL'? False
Imports System.Globalization
Imports System.Threading

Module Program
    Sub Main()
        Dim name As String = "Bill"

        Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
        Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}")
        Console.WriteLine($"   Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}")
        Console.WriteLine($"   Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", True, Nothing)}")
        Console.WriteLine()

        Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")
        Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}")
        Console.WriteLine($"   Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}")
        Console.WriteLine($"   Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", True, Nothing)}")
    End Sub

End Module

' The example displays the following output:
'
'     Culture = English (United States)
'        Is 'Bill' the same as 'BILL'? True
'        Does 'Bill' start with 'BILL'? True
'     
'     Culture = Turkish (Türkiye)
'        Is 'Bill' the same as 'BILL'? True
'        Does 'Bill' start with 'BILL'? False

Toto porovnání může způsobit významné problémy, pokud je jazyková verze neúmyslně používána v nastavení citlivém na zabezpečení, jako v následujícím příkladu. Volání metody, jako IsFileURI("file:") je například vrácení true , pokud je aktuální jazyková verze angličtina USA, ale false pokud je aktuální jazyková verze turečtina. V tureckém systému by tedy někdo mohl obejít bezpečnostní opatření, která blokují přístup k identifikátorům URI bez rozlišování malých a malých písmen, které začínají na "FILE:".

public static bool IsFileURI(string path) =>
    path.StartsWith("FILE:", true, null);
Public Shared Function IsFileURI(path As String) As Boolean
    Return path.StartsWith("FILE:", True, Nothing)
End Function

V tomto případě, protože "file:" je určen k interpretaci jako nejazyčný identifikátor nerozlišující jazykovou verzi, měl by být kód napsán, jak je znázorněno v následujícím příkladu:

public static bool IsFileURI(string path) =>
    path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
Public Shared Function IsFileURI(path As String) As Boolean
    Return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase)
End Function

Operace řadových řetězců

Určení StringComparison.Ordinal nebo StringComparison.OrdinalIgnoreCase hodnoty ve volání metody označuje nejazyčné porovnání, ve kterém jsou vlastnosti přirozeného jazyka ignorovány. Metody, které jsou vyvolány s těmito StringComparison hodnotami základní řetězcové operace rozhodnutí o jednoduchých porovnávání bajtů místo dělení písmen nebo ekvivalence tabulek, které jsou parametrizovány jazykovou verzí. Ve většině případů je tento přístup nejvhodnější pro zamýšlenou interpretaci řetězců a zároveň rychlejší a spolehlivější kód.

Řadové porovnání jsou porovnání řetězců, ve kterých se každý bajt každého řetězce porovnává bez jazykové interpretace; Například "windows" neodpovídá "Windows". Jedná se v podstatě o volání funkce modulu runtime strcmp jazyka C. Toto porovnání použijte, když kontext určuje, že řetězce by se měly přesně shodovat nebo vyžadují konzervativní odpovídající zásady. Kromě toho je pořadové porovnání nejrychlejší operací porovnání, protože při určování výsledku nepoužívá žádná jazyková pravidla.

Řetězce v .NET můžou obsahovat vložené znaky null (a další netiskné znaky). Jedním z nejjasnějších rozdílů mezi řadovým a jazykovým porovnáním (včetně porovnání, která používají invariantní jazykovou verzi) je zpracování vložených znaků null v řetězci. Tyto znaky jsou ignorovány při použití String.Compare a String.Equals metod k provádění porovnání citlivých na jazykovou verzi (včetně porovnání, která používají invariantní jazykovou verzi). Výsledkem je, že řetězce, které obsahují vložené znaky null, se dají považovat za řetězce, které ne. Vložené netisknutné znaky mohou být vynechány pro účely metod porovnání řetězců, například String.StartsWith.

Důležité

I když metody porovnání řetězců ignorují vložené znaky null, metody vyhledávání řetězců, jako String.Containsje , String.EndsWithString.IndexOf, String.LastIndexOfa String.StartsWith ne.

Následující příklad provádí porovnání řetězce "Aa" s podobným řetězcem, který obsahuje několik vložených znaků null mezi "A" a "a", a ukazuje, jak jsou tyto dva řetězce považovány za stejné:

string str1 = "Aa";
string str2 = "A" + new string('\u0000', 3) + "a";

Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo("en-us");

Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):");
Console.WriteLine("   With String.Compare:");
Console.WriteLine($"      Current Culture: {string.Compare(str1, str2, StringComparison.CurrentCulture)}");
Console.WriteLine($"      Invariant Culture: {string.Compare(str1, str2, StringComparison.InvariantCulture)}");
Console.WriteLine("   With String.Equals:");
Console.WriteLine($"      Current Culture: {string.Equals(str1, str2, StringComparison.CurrentCulture)}");
Console.WriteLine($"      Invariant Culture: {string.Equals(str1, str2, StringComparison.InvariantCulture)}");

string ShowBytes(string value)
{
   string hexString = string.Empty;
   for (int index = 0; index < value.Length; index++)
   {
      string result = Convert.ToInt32(value[index]).ToString("X4");
      result = string.Concat(" ", result.Substring(0,2), " ", result.Substring(2, 2));
      hexString += result;
   }
   return hexString.Trim();
}

// The example displays the following output:
//     Comparing 'Aa' (00 41 00 61) and 'Aa' (00 41 00 00 00 00 00 00 00 61):
//        With String.Compare:
//           Current Culture: 0
//           Invariant Culture: 0
//        With String.Equals:
//           Current Culture: True
//           Invariant Culture: True

Module Program
    Sub Main()
        Dim str1 As String = "Aa"
        Dim str2 As String = "A" & New String(Convert.ToChar(0), 3) & "a"

        Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):")
        Console.WriteLine("   With String.Compare:")
        Console.WriteLine($"      Current Culture: {String.Compare(str1, str2, StringComparison.CurrentCulture)}")
        Console.WriteLine($"      Invariant Culture: {String.Compare(str1, str2, StringComparison.InvariantCulture)}")
        Console.WriteLine("   With String.Equals:")
        Console.WriteLine($"      Current Culture: {String.Equals(str1, str2, StringComparison.CurrentCulture)}")
        Console.WriteLine($"      Invariant Culture: {String.Equals(str1, str2, StringComparison.InvariantCulture)}")
    End Sub

    Function ShowBytes(str As String) As String
        Dim hexString As String = String.Empty

        For ctr As Integer = 0 To str.Length - 1
            Dim result As String = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
            result = String.Concat(" ", result.Substring(0, 2), " ", result.Substring(2, 2))
            hexString &= result
        Next

        Return hexString.Trim()
    End Function

    ' The example displays the following output:
    '     Comparing 'Aa' (00 41 00 61) and 'Aa' (00 41 00 00 00 00 00 00 00 61):
    '        With String.Compare:
    '           Current Culture: 0
    '           Invariant Culture: 0
    '        With String.Equals:
    '           Current Culture: True
    '           Invariant Culture: True
End Module

Při použití řadového porovnání se však řetězce nepovažují za stejné, jak ukazuje následující příklad:

string str1 = "Aa";
string str2 = "A" + new String('\u0000', 3) + "a";

Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):");
Console.WriteLine("   With String.Compare:");
Console.WriteLine($"      Ordinal: {string.Compare(str1, str2, StringComparison.Ordinal)}");
Console.WriteLine("   With String.Equals:");
Console.WriteLine($"      Ordinal: {string.Equals(str1, str2, StringComparison.Ordinal)}");

string ShowBytes(string str)
{
    string hexString = string.Empty;
    for (int ctr = 0; ctr < str.Length; ctr++)
    {
        string result = Convert.ToInt32(str[ctr]).ToString("X4");
        result = " " + result.Substring(0, 2) + " " + result.Substring(2, 2);
        hexString += result;
    }
    return hexString.Trim();
}

// The example displays the following output:
//    Comparing 'Aa' (00 41 00 61) and 'A   a' (00 41 00 00 00 00 00 00 00 61):
//       With String.Compare:
//          Ordinal: 97
//       With String.Equals:
//          Ordinal: False
Module Program
    Sub Main()
        Dim str1 As String = "Aa"
        Dim str2 As String = "A" & New String(Convert.ToChar(0), 3) & "a"

        Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):")
        Console.WriteLine("   With String.Compare:")
        Console.WriteLine($"      Ordinal: {String.Compare(str1, str2, StringComparison.Ordinal)}")
        Console.WriteLine("   With String.Equals:")
        Console.WriteLine($"      Ordinal: {String.Equals(str1, str2, StringComparison.Ordinal)}")
    End Sub

    Function ShowBytes(str As String) As String
        Dim hexString As String = String.Empty

        For ctr As Integer = 0 To str.Length - 1
            Dim result As String = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
            result = String.Concat(" ", result.Substring(0, 2), " ", result.Substring(2, 2))
            hexString &= result
        Next

        Return hexString.Trim()
    End Function

    ' The example displays the following output:
    '    Comparing 'Aa' (00 41 00 61) and 'A   a' (00 41 00 00 00 00 00 00 00 61):
    '       With String.Compare:
    '          Ordinal: 97
    '       With String.Equals:
    '          Ordinal: False
End Module

Porovnání řadových řad nerozlišují malá a velká a malá písmena jsou dalším nejkonkonzervativnějším přístupem. Tato porovnání ignorují většinu případů; Například "windows" odpovídá "Windows". Při práci se znaky ASCII je tato zásada ekvivalentní StringComparison.Ordinal, s tím rozdílem, že ignoruje obvyklé písmena ASCII. Proto jakýkoli znak v [A, Z] (\u0041-\u005A) odpovídá odpovídajícímu znaku v [a,z] (\u0061-\007A). Velikost velikostí mimo rozsah ASCII používá invariantní tabulky jazykové verze. Proto následující porovnání:

string.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)

je ekvivalentní (ale rychlejší než) toto porovnání:

string.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal);
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal)

Tato porovnání jsou stále velmi rychlá.

Binární StringComparison.Ordinal hodnoty použijte StringComparison.OrdinalIgnoreCase přímo a nejlépe se hodí pro porovnávání. Pokud si nejste jistí nastavením porovnání, použijte jednu z těchto dvou hodnot. Vzhledem k tomu, že ale provádějí porovnání bajtů po bajtech, neřadí se podle lingvistického pořadí řazení (například anglického slovníku), ale podle binárního pořadí řazení. Výsledky můžou být ve většině kontextů liché, pokud se zobrazí uživatelům.

Ordinální sémantika je výchozím nastavením přetížení String.Equals , která nezahrnují StringComparison argument (včetně operátoru rovnosti). V každém případě doporučujeme volat přetížení, které má StringComparison parametr.

Řetězcové operace, které používají neutrální jazykovou verzi

Porovnání s invariantní jazykovou verzí používají CompareInfo vlastnost vrácenou statickou CultureInfo.InvariantCulture vlastností. Toto chování je stejné ve všech systémech; překládá všechny znaky mimo rozsah do toho, co se domnívá, že jsou ekvivalentní invariantní znaky. Tato zásada může být užitečná pro udržování jedné sady chování řetězců napříč jazykovými verzemi, ale často poskytuje neočekávané výsledky.

Porovnání nerozlišující malá a velká písmena s invariantní jazykovou verzí používají statickou CompareInfo vlastnost vrácenou statickou CultureInfo.InvariantCulture vlastností také pro informace o porovnání. Všechny rozdíly mezi těmito přeloženými znaky jsou ignorovány.

Porovnání, která používají StringComparison.InvariantCulture řetězce ASCII a StringComparison.Ordinal pracují shodně. Nicméně provede lingvistická rozhodnutí, StringComparison.InvariantCulture která nemusí být vhodná pro řetězce, které musí být interpretovány jako sada bajtů. Objekt CultureInfo.InvariantCulture.CompareInfo vytvoří metodu Compare interpretovat určité sady znaků jako ekvivalentní. Například následující ekvivalence je platná v rámci invariantní jazykové verze:

InvariantCulture: a + ̊ = å

MALÉ PÍSMENO LATINKY A znak "a" (\u0061), je-li vedle znaku COMBINING RING NAD znakem "+ " ̊" (\u030a), interpretován jako MALÉ PÍSMENO LATINKY A SE znakem å (\u00e5). Jak ukazuje následující příklad, toto chování se liší od řadového porovnání.

string separated = "\u0061\u030a";
string combined = "\u00e5";

Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}",
                  separated, combined,
                  string.Compare(separated, combined, StringComparison.InvariantCulture) == 0);

Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}",
                  separated, combined,
                  string.Compare(separated, combined, StringComparison.Ordinal) == 0);

// The example displays the following output:
//     Equal sort weight of a° and å using InvariantCulture: True
//     Equal sort weight of a° and å using Ordinal: False
Module Program
    Sub Main()
        Dim separated As String = ChrW(&H61) & ChrW(&H30A)
        Dim combined As String = ChrW(&HE5)

        Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}",
                          separated, combined,
                          String.Compare(separated, combined, StringComparison.InvariantCulture) = 0)

        Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}",
                          separated, combined,
                          String.Compare(separated, combined, StringComparison.Ordinal) = 0)

        ' The example displays the following output:
        '     Equal sort weight of a° and å using InvariantCulture: True
        '     Equal sort weight of a° and å using Ordinal: False
    End Sub
End Module

Při interpretaci názvůsouborůchm souborům se může objevit při interpretaci názvů souborů, souborů cookie nebo čehokoli jiného, kde se může objevit kombinace, například "å"

Při vyvážení má invariantní jazyková verze několik vlastností, díky kterým je užitečné pro porovnání. Porovnává jazykově relevantním způsobem, což brání v zaručení úplné symbolické ekvivalence, ale není to volba pro zobrazení v žádné jazykové verzi. Jedním z několika důvodů, proč je použít StringComparison.InvariantCulture k porovnání, je zachovat seřazená data pro křížově identické zobrazení. Pokud například velký datový soubor, který obsahuje seznam seřazených identifikátorů pro zobrazení, doprovází aplikaci, přidání do tohoto seznamu by vyžadovalo vložení s neutrálním řazením ve stylu.

Volba člena StringComparison pro volání metody

Následující tabulka popisuje mapování z kontextu sémantického řetězce na člen výčtu StringComparison :

Data Chování Odpovídající System.StringComparison

hodnota
Interní identifikátory citlivé na malá a velká písmena.

Identifikátory rozlišující malá a velká písmena ve standardech, jako je XML a HTTP.

Nastavení související se zabezpečením s rozlišováním velkých a malých písmen
Nejazyčný identifikátor, kde se bajty přesně shodují. Ordinal
Interní identifikátory nerozlišující malá a velká písmena.

Identifikátory nerozlišující malá a velká písmena ve standardech, jako je XML a HTTP.

Cesty k souborům

Klíče a hodnoty registru.

Proměnné prostředí.

Identifikátory prostředků (například popisovač názvů)

Nastavení související se zabezpečením nerozlišují malá a velká písmena.
Nejazyčný identifikátor, pokud je případ irelevantní. OrdinalIgnoreCase
Některá trvalá, lingvisticky relevantní data.

Zobrazení lingvistických dat, která vyžadují pevné pořadí řazení
Kulturní nezávislá data, která jsou stále lingvisticky relevantní. InvariantCulture

nebo

InvariantCultureIgnoreCase
Data zobrazená uživateli

Většina uživatelských vstupů.
Data, která vyžadují místní lingvistické celní údaje. CurrentCulture

nebo

CurrentCultureIgnoreCase

Běžné metody porovnání řetězců v .NET

Následující části popisují metody, které se nejčastěji používají pro porovnání řetězců.

String.compare

Výchozí interpretace: StringComparison.CurrentCulture.

Vzhledem k tomu, že operace nejvíce centrální pro interpretaci řetězců, všechny instance těchto volání metody by měly být zkoumány, aby bylo možné určit, zda řetězce mají být interpretovány podle aktuální jazykové verze, nebo se oddělí od jazykové verze (symbolicky). Obvykle se jedná o druhou a StringComparison.Ordinal místo toho by se mělo použít porovnání.

Třída System.Globalization.CompareInfo , která je vrácena CultureInfo.CompareInfo vlastností, zahrnuje také metodu Compare , která poskytuje velký počet odpovídajících možností (pořadové číslování, ignorování prázdných znaků, ignorování typu kana atd.) pomocí výčtu příznaku CompareOptions .

String.CompareTo

Výchozí interpretace: StringComparison.CurrentCulture.

Tato metoda v současné době nenabízí přetížení, které určuje StringComparison typ. Obvykle je možné tuto metodu převést na doporučený String.Compare(String, String, StringComparison) formulář.

Typy, které implementují tuto metodu IComparable a IComparable<T> rozhraní. Vzhledem k tomu, že nenabízí možnost parametru StringComparison , implementace typů často umožňuje uživateli zadat v jeho konstruktoru StringComparer . Následující příklad definuje FileName třídu, jejíž konstruktor třídy obsahuje StringComparer parametr. Tento StringComparer objekt se pak použije v FileName.CompareTo metodě.

class FileName : IComparable
{
    private readonly StringComparer _comparer;

    public string Name { get; }

    public FileName(string name, StringComparer? comparer)
    {
        if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));

        Name = name;

        if (comparer != null)
            _comparer = comparer;
        else
            _comparer = StringComparer.OrdinalIgnoreCase;
    }

    public int CompareTo(object? obj)
    {
        if (obj == null) return 1;

        if (obj is not FileName)
            return _comparer.Compare(Name, obj.ToString());
        else
            return _comparer.Compare(Name, ((FileName)obj).Name);
    }
}
Class FileName
    Implements IComparable

    Private ReadOnly _comparer As StringComparer

    Public ReadOnly Property Name As String

    Public Sub New(name As String, comparer As StringComparer)
        If (String.IsNullOrEmpty(name)) Then Throw New ArgumentNullException(NameOf(name))

        Me.Name = name

        If comparer IsNot Nothing Then
            _comparer = comparer
        Else
            _comparer = StringComparer.OrdinalIgnoreCase
        End If
    End Sub

    Public Function CompareTo(obj As Object) As Integer Implements IComparable.CompareTo
        If obj Is Nothing Then Return 1

        If TypeOf obj IsNot FileName Then
            Return _comparer.Compare(Name, obj.ToString())
        Else
            Return _comparer.Compare(Name, DirectCast(obj, FileName).Name)
        End If
    End Function
End Class

String.Equals

Výchozí interpretace: StringComparison.Ordinal.

Třída String umožňuje otestovat rovnost voláním přetížení statické metody nebo metody instance Equals nebo pomocí statického operátoru rovnosti. Přetížení a operátor ve výchozím nastavení používají řadové porovnání. Přesto však doporučujeme volat přetížení, které explicitně určuje StringComparison typ, i když chcete provést řadové porovnání; to usnadňuje vyhledávání kódu pro určitou interpretaci řetězců.

String.ToUpper a String.ToLower

Výchozí interpretace: StringComparison.CurrentCulture.

Při použití String.ToUpper() a String.ToLower() metod buďte opatrní, protože vynucení řetězce na velká nebo malá písmena se často používá jako malá normalizace pro porovnávání řetězců bez ohledu na malá písmena. Pokud ano, zvažte použití porovnání bez rozlišování velkých a malých písmen.

String.ToLowerInvariant K String.ToUpperInvariant dispozici jsou také tyto metody. ToUpperInvariant je standardní způsob, jak normalizovat případ. Porovnání provedená pomocí StringComparison.OrdinalIgnoreCase jsou chování složení dvou volání: volání ToUpperInvariant obou řetězcových argumentů a porovnání pomocí StringComparison.Ordinal.

Přetížení jsou také k dispozici pro převod na velká a malá písmena v konkrétní jazykové verzi předáním CultureInfo objektu, který představuje danou jazykovou verzi metodě.

Char.ToUpper a Char.ToLower

Výchozí interpretace: StringComparison.CurrentCulture.

Metody Char.ToUpper(Char) a Char.ToLower(Char) metody fungují podobně String.ToUpper()String.ToLower() jako metody popsané v předchozí části.

String.StartsWith a String.EndsWith

Výchozí interpretace: StringComparison.CurrentCulture.

Ve výchozím nastavení obě tyto metody provádějí porovnání citlivé na jazykovou verzi. Konkrétně mohou ignorovat netisknutné znaky.

String.IndexOf a String.LastIndexOf

Výchozí interpretace: StringComparison.CurrentCulture.

Existuje nedostatek konzistence v tom, jak výchozí přetížení těchto metod provádí porovnání. Všechny String.IndexOf a String.LastIndexOf metody, které obsahují Char parametr, provádějí řadové porovnání, ale výchozí String.IndexOf a String.LastIndexOf metody, které obsahují String parametr, provádějí porovnání citlivé na jazykovou verzi.

Pokud zavoláte nebo String.LastIndexOf(String) metodu String.IndexOf(String) a předáte jí řetězec pro vyhledání v aktuální instanci, doporučujeme volat přetížení, které explicitně určuje StringComparison typ. Přetížení, která obsahují Char argument, neumožňují zadat StringComparison typ.

Metody, které provádějí porovnání řetězců nepřímo

Některé neřetězcové metody, které mají porovnání řetězců jako centrální operace, používají StringComparer typ. Třída StringComparer obsahuje šest statických vlastností, které vracejí StringComparer instance, jejichž StringComparer.Compare metody provádějí následující typy porovnání řetězců:

Array.Sort a Array.BinarySearch

Výchozí interpretace: StringComparison.CurrentCulture.

Při ukládání jakýchkoli dat v kolekci nebo čtení trvalých dat ze souboru nebo databáze do kolekce může přepnutí aktuální jazykové verze zneplatnit invarianty v kolekci. Metoda Array.BinarySearch předpokládá, že prvky v poli, které mají být prohledány, jsou již seřazeny. Chcete-li seřadit libovolný prvek řetězce v poli, Array.Sort metoda volá metodu String.Compare pořadí jednotlivých prvků. Použití porovnávače citlivého na jazykovou verzi může být nebezpečné, pokud se jazyková verze změní mezi časem řazení pole a jeho obsahem. Například v následujícím kódu funguje úložiště a načítání s porovnávačem, který je implicitně poskytován vlastností Thread.CurrentThread.CurrentCulture . Pokud se jazyková verze může mezi voláními a StoreNamesDoesNameExista , a zejména v případě, že obsah pole je trvalý někde mezi dvěma voláními metody, binární vyhledávání může selhat.

// Incorrect
string[] _storedNames;

public void StoreNames(string[] names)
{
    _storedNames = new string[names.Length];

    // Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length);

    Array.Sort(_storedNames); // Line A
}

public bool DoesNameExist(string name) =>
    Array.BinarySearch(_storedNames, name) >= 0; // Line B
' Incorrect
Dim _storedNames As String()

Sub StoreNames(names As String())
    ReDim _storedNames(names.Length - 1)

    ' Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length)

    Array.Sort(_storedNames) ' Line A
End Sub

Function DoesNameExist(name As String) As Boolean
    Return Array.BinarySearch(_storedNames, name) >= 0 ' Line B
End Function

V následujícím příkladu se zobrazí doporučená varianta, která používá stejnou metodu porovnání bez rozlišení jazykové verze k seřazení i vyhledávání v poli. Kód změny se odráží na řádcích označených Line A a Line B v obou příkladech.

// Correct
string[] _storedNames;

public void StoreNames(string[] names)
{
    _storedNames = new string[names.Length];

    // Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length);

    Array.Sort(_storedNames, StringComparer.Ordinal); // Line A
}

public bool DoesNameExist(string name) =>
    Array.BinarySearch(_storedNames, name, StringComparer.Ordinal) >= 0; // Line B
' Correct
Dim _storedNames As String()

Sub StoreNames(names As String())
    ReDim _storedNames(names.Length - 1)

    ' Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length)

    Array.Sort(_storedNames, StringComparer.Ordinal) ' Line A
End Sub

Function DoesNameExist(name As String) As Boolean
    Return Array.BinarySearch(_storedNames, name, StringComparer.Ordinal) >= 0 ' Line B
End Function

Pokud jsou tato data trvalá a přesunutá mezi jazykovými verzemi a řazení se používají k prezentování těchto dat uživateli, můžete zvážit použití StringComparison.InvariantCulture, které funguje v lingvisticky pro lepší výstup uživatele, ale změny v jazykové verzi nejsou ovlivněné. Následující příklad upraví dva předchozí příklady tak, aby používaly invariantní jazykovou verzi pro řazení a prohledávání pole.

// Correct
string[] _storedNames;

public void StoreNames(string[] names)
{
    _storedNames = new string[names.Length];

    // Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length);

    Array.Sort(_storedNames, StringComparer.InvariantCulture); // Line A
}

public bool DoesNameExist(string name) =>
    Array.BinarySearch(_storedNames, name, StringComparer.InvariantCulture) >= 0; // Line B
' Correct
Dim _storedNames As String()

Sub StoreNames(names As String())
    ReDim _storedNames(names.Length - 1)

    ' Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length)

    Array.Sort(_storedNames, StringComparer.InvariantCulture) ' Line A
End Sub

Function DoesNameExist(name As String) As Boolean
    Return Array.BinarySearch(_storedNames, name, StringComparer.InvariantCulture) >= 0 ' Line B
End Function

Příklad kolekcí: Konstruktor hashtable

Řetězce hash poskytují druhý příklad operace, která je ovlivněna způsobem porovnání řetězců.

Následující příklad vytvoří Hashtable instanci objektu předáním objektu StringComparer , který je vrácen vlastností StringComparer.OrdinalIgnoreCase . Protože třída StringComparer , která je odvozena z StringComparer implements rozhraní IEqualityComparer , jeho GetHashCode metoda se používá k výpočtu hash kódu řetězců v tabulce hash.

using System.IO;
using System.Collections;

const int InitialCapacity = 100;

Hashtable creationTimeByFile = new(InitialCapacity, StringComparer.OrdinalIgnoreCase);
string directoryToProcess = Directory.GetCurrentDirectory();

// Fill the hash table
PopulateFileTable(directoryToProcess);

// Get some of the files and try to find them with upper cased names
foreach (var file in Directory.GetFiles(directoryToProcess))
    PrintCreationTime(file.ToUpper());


void PopulateFileTable(string directory)
{
    foreach (string file in Directory.GetFiles(directory))
        creationTimeByFile.Add(file, File.GetCreationTime(file));
}

void PrintCreationTime(string targetFile)
{
    object? dt = creationTimeByFile[targetFile];

    if (dt is DateTime value)
        Console.WriteLine($"File {targetFile} was created at time {value}.");
    else
        Console.WriteLine($"File {targetFile} does not exist.");
}
Imports System.IO

Module Program
    Const InitialCapacity As Integer = 100

    Private ReadOnly s_creationTimeByFile As New Hashtable(InitialCapacity, StringComparer.OrdinalIgnoreCase)
    Private ReadOnly s_directoryToProcess As String = Directory.GetCurrentDirectory()

    Sub Main()
        ' Fill the hash table
        PopulateFileTable(s_directoryToProcess)

        ' Get some of the files and try to find them with upper cased names
        For Each File As String In Directory.GetFiles(s_directoryToProcess)
            PrintCreationTime(File.ToUpper())
        Next
    End Sub

    Sub PopulateFileTable(directoryPath As String)
        For Each file As String In Directory.GetFiles(directoryPath)
            s_creationTimeByFile.Add(file, IO.File.GetCreationTime(file))
        Next
    End Sub

    Sub PrintCreationTime(targetFile As String)
        Dim dt As Object = s_creationTimeByFile(targetFile)

        If TypeOf dt Is Date Then
            Console.WriteLine($"File {targetFile} was created at time {DirectCast(dt, Date)}.")
        Else
            Console.WriteLine($"File {targetFile} does not exist.")
        End If
    End Sub
End Module

Viz také