Sdílet prostřednictvím


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ů. Ale řazení nebo porovnávání řetězců není vždy citlivé na kulturní kontext. 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 řetězcová data nezávislá na kulturních specifikách, 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 byly kulturně závislá, může kód aplikace podléhat drobným chybám, špatnému výkonu a v některých případech i bezpečnostním problémům.

Tento článek zkoumá metody řazení, porovnávání a práci s velikostí písmen v prostředí .NET, obsahuje doporučení pro výběr vhodné metody zpracování řetězců a poskytuje dodatečné informace o těchto metodách.

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í.

Návod

Porovnání provádí různé metody související s řetězci. Mezi příklady patří String.Equals, String.Compare, String.IndexOfa 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 založené na StringComparison.InvariantCulture. Jednou z několika výjimek je, když budete uchovávat lingvisticky smysluplná, ale kulturně nezávislá data.
  • Nepoužívejte přetíženou metodu String.Compare nebo CompareTo a otestujte, zda je návratová hodnota nulová, abyste zjistili, zda jsou dva řetězce stejné.

Návod

Pravidla analýzy kódu CA1307, CA1309 a CA1310 pomáhají identifikovat weby volání, kde se lingvistický porovnávač používá neúmyslně. Pokud je chcete povolit a zobrazit porušení jako chyby sestavení, nastavte v souboru projektu následující vlastnosti:

<PropertyGroup>
  <AnalysisMode>All</AnalysisMode>
  <WarningsAsErrors>$(WarningsAsErrors);CA1307;CA1309;CA1310</WarningsAsErrors>
</PropertyGroup>

Explicitně zadejte 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ýčtového typu StringComparison.

StringComparison člen Popis
CurrentCulture Provede porovnání s rozlišováním velkých a malých písmen pomocí aktuálního kulturního nastavení.
CurrentCultureIgnoreCase Provede porovnání nerozlišující malá a velká písmena pomocí aktuální kultury.
InvariantCulture Provádí porovnání s rozlišováním velkých a malých písmen pomocí invariantní kultury.
InvariantCultureIgnoreCase Provede porovnání bez rozlišování velkých a malých písmen použitím invariantní kultury.
Ordinal Provede řadové porovnání.
OrdinalIgnoreCase Provádí pořadové porovnávání bez rozlišování malých a velkých písmen.

Například metoda IndexOf, 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 (ta, která hledají Char v instanci řetězce) provádějí porovnání podle pořadí, zatímco jiná (ta, která hledají řetězec v dané instanci) jsou závislá na kulturním kontextu. Je obtížné si zapamatovat, která metoda používá jakou výchozí hodnotu, a přetížení je snadné zaměnit.

  • 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 zda rozdíl mezi případem mezi url.Scheme 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ávání řetězců je klíčovým prvkem mnoha operací souvisejících s řetězci, zejména řazení a ověřování rovnosti. Řetězce jsou seřazeny v určeném pořadí: Pokud se "my" objeví před "řetězec" v seřazeném seznamu řetězců, musí "my" být menší nebo se rovnat "řetězci". Porovnání navíc implicitně definuje rovnost. Operace porovnání vrátí nulu pro řetězce, které považuje za stejné. 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 váhového řazení, sadu textových souborů, které obsahují informace o váhách znaků používaných při řazení a porovnávání v operačních systémech Windows, a Výchozí tabulka kolace Unicode, nejnovější verzi tabulky váhového řazení pro Linux a macOS. Konkrétní verze tabulky priorit řazení v Linuxu a macOS závisí na verzi knihoven International Components for Unicode nainstalovaných na systému. Informace o verzích ICU a verzích Unicode, které implementují, naleznete v oddílu 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 ordinační nebo která jsou založena na konvencích rozlišování velikosti písmen a řazení aktuální kultury nebo invariantní kultury (kultury nezávislé na národním prostředí na základě anglického jazyka), mohou způsobit různé výsledky.

Kromě toho porovnání řetězců s různými verzemi .NET nebo .NET na různých operačních systémech nebo verzích operačního systému může vracet různé výsledky. .NET používá knihovnu International Components for Unicode (ICU) pro porovnání lingvistických řetězců na všech podporovaných platformách. Další informace naleznete v tématu Řetězce a Standard Unicode a .NET globalizace a ICU.

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

Jedním z kritérií je použití konvencí aktuální kultury 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 kulturu nenastaví, použije 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.

Když se změní kultura, změní se v .NET chování porovnání a velkých a malých písmen. 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štinou ("sv-SE"). Všimněte si, že slova "ångström", "Windows" a "Visual Studio" se objevují na různých pozicích v seřazených polí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 zavolat dané přetížení, které má parametr StringComparison, aby byl jasný záměr volání metody.

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 problém Turkish-I.

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 psaní velkých písmen se rychle stane výchozím nastavením pro někoho, kdo programuje v takovém prostředí. Nicméně, turečtina ("tr-TR") abeceda obsahuje "I s tečkou" znak "İ" (\u0130), což je hlavní verze "i". Turečtina obsahuje také malé písmeno 'i bez tečky', znak 'ı' (\u0131), které se používá jako velké písmeno '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 různosti mezi kulturami. Pokud jsou porovnávaná data nejazyková, použití výchozích přetížení může vést k nežádoucím výsledkům, jak ukazuje následující pokus o provedení porovnání řetězců "bill" a "BILL" bez rozlišování velikosti písmen.

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 kultura neúmyslně používána v prostředích citlivých na zabezpečení, jako v následujícím příkladu. Volání metody, jako je IsFileURI("file:"), vrátí true, pokud je aktuální kulturní prostředí angličtina (USA), ale false, pokud je aktuální kulturní prostředí turečtina. V tureckých systémech by tedy někdo mohl obejít bezpečnostní opatření, která blokují přístup k URI bez rozlišování velkých a malých písmen, které začínají „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, zakládají rozhodování o operacích na řetězcích na jednoduchých porovnáváních bajtů namísto tabulek velikostí písmen nebo ekvivalence, které jsou parametrizovány kulturními zvyklostmi. 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 strcmp běhového prostředí jazyka C. Toto porovnání použijte, když kontext vyžaduje, aby se řetězce přesně shodovaly a vyžaduje konzervativní politiku srovnávání. 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.

Porovnávač OrdinalIgnoreCase stále funguje po znacích, ale eliminuje rozdíly mezi velkými a malými písmeny při provádění operace. Pod porovnávačem se dvojice znaků OrdinalIgnoreCase a 'd' porovnávají jako 'D', stejně jako dvojice znaků a 'á'. Neakcentovaný znak 'a' se ale porovnává jako nerovný akcentovanému znaku 'á'.

V následující tabulce jsou uvedené některé příklady:

Řetězec 1 Řetězec 2 Ordinal Porovnání OrdinalIgnoreCase Porovnání
"dog" "dog" rovný rovný
"dog" "Dog" není rovno rovný
"resume" "résumé" není rovno není rovno

Unicode také umožňuje řetězcům mít několik různých reprezentací v paměti. Například e-akutní (é) lze reprezentovat dvěma možnými způsoby:

  • Jeden literálový 'é' znak (také napsaný jako '\u00E9').
  • Literální neakcentovaný 'e' znak následovaný kombinačním akcentovým modifikátorem '\u0301'.

To znamená, že následující čtyři řetězce se zobrazí jako "résumé", i když se jejich základní části liší. Řetězce používají kombinaci literálních 'é' znaků nebo literálních nepřízvučných 'e' znaků plus modifikátor kombinující akcent '\u0301'.

  • "r\u00E9sum\u00E9"
  • "r\u00E9sume\u0301"
  • "re\u0301sum\u00E9"
  • "re\u0301sume\u0301"

V rámci řadového porovnání není žádný z těchto řetězců stejný jako jiný. Je to proto, že všechny obsahují různé podkladové sekvence znaků, i když se vykreslují na obrazovku, všechny vypadají stejně.

Při provádění string.IndexOf(..., StringComparison.Ordinal) operace modul runtime hledá přesnou shodu podřetěžce. Výsledky jsou následující.

Console.WriteLine("resume".IndexOf('e', StringComparison.Ordinal)); // "resume": prints '1'
Console.WriteLine("r\u00E9sum\u00E9".IndexOf('e', StringComparison.Ordinal)); // "résumé": prints '-1'
Console.WriteLine("r\u00E9sume\u0301".IndexOf('e', StringComparison.Ordinal)); // "résumé": prints '5'
Console.WriteLine("re\u0301sum\u00E9".IndexOf('e', StringComparison.Ordinal)); // "résumé": prints '1'
Console.WriteLine("re\u0301sume\u0301".IndexOf('e', StringComparison.Ordinal)); // "résumé": prints '1'
Console.WriteLine("resume".IndexOf('e', StringComparison.OrdinalIgnoreCase)); // "resume": prints '1'
Console.WriteLine("r\u00E9sum\u00E9".IndexOf('e', StringComparison.OrdinalIgnoreCase)); // "résumé": prints '-1'
Console.WriteLine("r\u00E9sume\u0301".IndexOf('e', StringComparison.OrdinalIgnoreCase)); // "résumé": prints '5'
Console.WriteLine("re\u0301sum\u00E9".IndexOf('e', StringComparison.OrdinalIgnoreCase)); // "résumé": prints '1'
Console.WriteLine("re\u0301sume\u0301".IndexOf('e', StringComparison.OrdinalIgnoreCase)); // "résumé": prints '1'
Sub IndexOfExample()
    Console.WriteLine("resume".IndexOf("e"c, StringComparison.Ordinal)) ' "resume": prints '1'
    Console.WriteLine(("r" & ChrW(&HE9) & "sum" & ChrW(&HE9)).IndexOf("e"c, StringComparison.Ordinal)) ' "résumé": prints '-1'
    Console.WriteLine(("r" & ChrW(&HE9) & "sume" & ChrW(&H301)).IndexOf("e"c, StringComparison.Ordinal)) ' "résumé": prints '5'
    Console.WriteLine(("re" & ChrW(&H301) & "sum" & ChrW(&HE9)).IndexOf("e"c, StringComparison.Ordinal)) ' "résumé": prints '1'
    Console.WriteLine(("re" & ChrW(&H301) & "sume" & ChrW(&H301)).IndexOf("e"c, StringComparison.Ordinal)) ' "résumé": prints '1'
    Console.WriteLine("resume".IndexOf("e"c, StringComparison.OrdinalIgnoreCase)) ' "resume": prints '1'
    Console.WriteLine(("r" & ChrW(&HE9) & "sum" & ChrW(&HE9)).IndexOf("e"c, StringComparison.OrdinalIgnoreCase)) ' "résumé": prints '-1'
    Console.WriteLine(("r" & ChrW(&HE9) & "sume" & ChrW(&H301)).IndexOf("e"c, StringComparison.OrdinalIgnoreCase)) ' "résumé": prints '5'
    Console.WriteLine(("re" & ChrW(&H301) & "sum" & ChrW(&HE9)).IndexOf("e"c, StringComparison.OrdinalIgnoreCase)) ' "résumé": prints '1'
    Console.WriteLine(("re" & ChrW(&H301) & "sume" & ChrW(&H301)).IndexOf("e"c, StringComparison.OrdinalIgnoreCase)) ' "résumé": prints '1'
End Sub

Pořadové rutiny pro vyhledávání a porovnání nejsou nikdy ovlivněny nastavením kultury aktuálního vlákna.

Řetězce v .NET můžou obsahovat vložené znaky null (a další netiskné znaky). Jedním z nejjasnějších rozdílů mezi ordinálním a kulturu zohledňujícím porovnáním (včetně porovnání, která používají invariantní kulturní verzi) je zpracování vložených nulových znaků v textovém řetězci. Tyto znaky jsou ignorovány při použití metod String.Compare a String.Equals k provádění porovnání citlivých na kulturní rozdíly (včetně porovnání, která používají neutrální jazykovou verzi). Výsledkem je, že řetězce, které obsahují vložené znaky null, mohou být považovány za rovné řetězcům, které je neobsahují. 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 jsou String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOfa String.StartsWith ne.

Následující příklad provádí kultuře přizpůsobené porovnání řetězce "Aa" s podobným řetězcem, který obsahuje několik vložených nulových znaků 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í podle pořadí, která nerozlišují velká a malá písmena, jsou dalším nejkonzervativně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í k StringComparison.Ordinal, s tím rozdílem, že ignoruje obvyklou velikost písmen ASCII. Proto libovolný znak v [A, Z] (\u0041-\u005A) odpovídá odpovídajícímu znaku v [a;z] (\u0061-\007A). Použití velkých a malých písmen mimo rozsah ASCII využívá invariantní tabulky kultury. 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á.

StringComparison.Ordinal i StringComparison.OrdinalIgnoreCase používají binární hodnoty přímo a jsou nejvhodnější 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 standardní pro přetížení String.Equals, které nezahrnují argument StringComparison (včetně operátoru rovnosti). V každém případě doporučujeme volat přetížení, které má parametr StringComparison.

Porovnání lingvistických řetězců

Lingvistické vyhledávání a porovnávací rutiny rozkládají řetězec do kompletačních prvků a provádějí vyhledávání nebo porovnání těchto prvků. Mapování mezi znaky řetězce a jeho prvky při kolaci nemusí být vždy 1:1. Například řetězec délky 2 se může skládat pouze z jednoho kompletačního prvku. Když jsou dva řetězce porovnávány lingvisticky, porovnávač zkontroluje, jestli mají oba prvky kolace řetězců stejný sémantický význam, i když jsou literální znaky řetězce odlišné.

Představte si řetězec "résumé" a čtyři různé reprezentace popsané v předchozí části. Následující tabulka uvádí jednotlivé reprezentace rozdělené do svých prvků kolace.

String Jako prvky kolace
"r\u00E9sum\u00E9" "r" + "\u00E9" + "s" + "u" + "m" + "\u00E9"
"r\u00E9sume\u0301" "r" + "\u00E9" + "s" + "u" + "m" + "e\u0301"
"re\u0301sum\u00E9" "r" + "e\u0301" + "s" + "u" + "m" + "\u00E9"
"re\u0301sume\u0301" "r" + "e\u0301" + "s" + "u" + "m" + "e\u0301"

Kolační prvek volně odpovídá tomu, co čtenáři považují za jeden znak nebo shluk znaků. Je koncepčně podobný grafémovému klastru, ale zahrnuje poněkud větší pojem.

V jazykovém porovnávači nejsou přesné shody nezbytné. Kompletační prvky se místo toho porovnávají na základě jejich sémantického významu. Lingvistický porovnávač například považuje podřetězce "\u00E9" a "e\u0301" za stejné, protože oba sémanticky znamenají "malé písmeno e s akcentem". To umožňuje metodě IndexOf porovnat podřetězec "e\u0301" ve větším řetězci, který obsahuje sémanticky ekvivalentní podřetězec "\u00E9", jak je znázorněno v následující ukázce kódu.

Console.WriteLine("r\u00E9sum\u00E9".IndexOf("e")); // "résumé": prints '-1' (not found)
Console.WriteLine("r\u00E9sum\u00E9".IndexOf("\u00E9")); // "résumé": prints '1'
Console.WriteLine("\u00E9".IndexOf("e\u0301")); // prints '0'
Sub IndexOfStringExample()
    Console.WriteLine(("r" & ChrW(&HE9) & "sum" & ChrW(&HE9)).IndexOf("e")) ' "résumé": prints '-1' (not found)
    Console.WriteLine(("r" & ChrW(&HE9) & "sum" & ChrW(&HE9)).IndexOf(ChrW(&HE9).ToString())) ' "résumé": prints '1'
    Console.WriteLine(ChrW(&HE9).ToString().IndexOf("e" & ChrW(&H301))) ' prints '0'
End Sub

V důsledku toho se můžou dva řetězce různých délek porovnávat stejně, pokud se použije lingvistické porovnání. Volající by si měli dát pozor, aby se v takových scénářích nesnažili zvláštně zpracovávat logiku týkající se délky řetězce.

Rutiny vyhledávání a porovnávání respektující kulturní prostředí jsou zvláštní formou lingvistického vyhledávání a porovnávacích rutin. Při využití porovnávače citlivého na jazykovou verzi je koncept řadicího prvku rozšířen tak, aby zahrnoval informace specifické pro danou kulturu.

Například v maďarské abecedě, když se dva znaky <dz> objeví za sebou, považují se za vlastní unikátní písmeno odlišné od <d> nebo <z>. To znamená, že když je <dz> zobrazen v řetězci, maďarské porovnávání s ním zachází jako s jedním kolace.

String Jako prvky kolace Poznámky
"endz" "e" + "n" + "d" + "z" (pomocí standardního lingvistického porovnávače)
"endz" "e" + "n" + "dz" (pomocí porovnávače s povědomím o maďarské kultuře)

Při použití porovnávače, který je si vědom kulturních specifik maďarštiny, řetězec "endz"nekončí podřetězcem "z", protože <dz> a <z> jsou považovány za třídicí prvky s odlišným sémantickým významem.

// Set thread culture to Hungarian
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("hu-HU");
Console.WriteLine("endz".EndsWith("z")); // Prints 'False'

// Set thread culture to invariant culture
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
Console.WriteLine("endz".EndsWith("z")); // Prints 'True'
' Set thread culture to Hungarian
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("hu-HU")
Console.WriteLine("endz".EndsWith("z")) ' Prints 'False'

' Set thread culture to invariant culture
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture
Console.WriteLine("endz".EndsWith("z")) ' Prints 'True'

Poznámka:

  • Chování: Porovnávače s jazykovým a kulturním povědomím mohou čas od času podléhat úpravám chování. IcU i starší zařízení NlS systému Windows se aktualizují tak, aby zohlednily, jak se světové jazyky mění. Další informace najdete v blogovém příspěvku o změnách dat národního prostředí (jazykové verze). Chování řadového porovnávače se nikdy nezmění, protože provádí přesné bitové vyhledávání a porovnání. Chování porovnávače OrdinalIgnoreCase se však může změnit, protože Unicode se rozšiřuje, aby zahrnulo více znakových sad a opravuje nedostatky v existujících datech o psaní velkých a malých písmen.
  • Použití: Porovnávače StringComparison.InvariantCulture a StringComparison.InvariantCultureIgnoreCase jsou lingvistické porovnávače, které nejsou citlivé na kulturní rozdíly. To znamená, že tito porovnávači chápou koncepty, jako je zvýrazněný znak é s více možnými podkladovými reprezentacemi, a že všechny takové reprezentace by měly být považovány za stejné. Jazykové porovnávače, které nejsou citlivé na jazykové nebo kulturní rozdíly, nebudou obsahovat speciální zpracování pro <dz> jako odlišné od <d> nebo <z>, jak je znázorněno výše. Nebudou také speciální znaky jako německý Eszett (ß).

.NET také nabízí invariantní režim globalizace. Tento režim výslovného souhlasu zakáže cesty kódu, které se zabývají lingvistickými vyhledávacími a porovnávanými rutinami. V tomto režimu používají všechny operace chování Ordinal nebo OrdinalIgnoreCase bez ohledu na to, jaký argument volající poskytne pomocí CultureInfo nebo StringComparison. Další informace najdete v tématu Možnosti konfigurace modulu runtime pro globalizaci a invariantní režim globalizace .NET Core.

Řetězcové operace, které používají invariantní jazykovou verzi

Porovnání s invariantní kulturou používá vlastnost CompareInfo, kterou vrací statická vlastnost CultureInfo.InvariantCulture. 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í konsistentního chování řetězců napříč kulturami, ale často poskytuje neočekávané výsledky.

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

Porovnání, která používají StringComparison.InvariantCulture a StringComparison.Ordinal fungují shodně s řetězci ASCII. StringComparison.InvariantCulture však provádí lingvistická rozhodnutí, která nemusí být vhodná pro řetězce, které musí být interpretovány jako sada bajtů. Objekt CultureInfo.InvariantCulture.CompareInfo způsobí, aby metoda Compare interpretovala určité sady znaků jako rovnocenné. Například následující ekvivalence je platná v rámci invariantní kulturní verze.

InvariantCulture: a + ̊ = å

Malé písmeno latinky "a" (\u0061), je-li vedle znaku SLOUČENÍ PRSTENU NAD "̊" (\u030a), je interpretováno jako malé písmeno latinky "a" s prstencem nad "å" (\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 {separated} and {combined} using InvariantCulture: {string.Compare(separated, combined, StringComparison.InvariantCulture) == 0}");

Console.WriteLine($"Equal sort weight of {separated} and {combined} using Ordinal: {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ů, souborů cookie nebo čehokoli jiného, kde se může objevit kombinace jako "å", nabízejí pořadová porovnání stále nejtransparentnější a nejvhodnější chování.

Celkově má neproměnná kultura několik vlastností, díky kterým je užitečná pro porovnání. Porovnává způsobem, který má jazykový význam, což brání zajistit úplnou symbolickou shodu, ale není to vhodná volba pro zobrazení v žádné kultuře. Jedním z mála důvodů použití StringComparison.InvariantCulture pro porovnání je zachování seřazených dat pro kulturně jednotné 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 řazením ve stylu invariant.

Výběr člena StringComparison

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

Údaje Chování Odpovídající System.StringComparison

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

Případově citlivé identifikátory ve standardech, jako je XML a HTTP.

Nastavení související se zabezpečením, která rozlišují velká a malá písmena.
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 názvy úchytů)

Nastavení související se zabezpečením nezávisle na velikosti písmen.
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é zvyky. CurrentCulture

nebo

CurrentCultureIgnoreCase

Vliv na zabezpečení

Pokud vaše aplikace používá rozhraní API řetězců k filtrování nebo řízení přístupu, použijte pořadové porovnání. Lingvistická porovnání založená na aktuální jazykové verzi můžou vést k neočekávaným výsledkům, které se liší podle platformy a národního prostředí. Vzory kódu jako následující můžou být náchylné k zneužití zabezpečení:

//
// THIS SAMPLE CODE IS INCORRECT.
// DO NOT USE IT IN PRODUCTION.
//
bool ContainsHtmlSensitiveCharacters(string input)
{
    if (input.IndexOf("<") >= 0) { return true; }
    if (input.IndexOf("&") >= 0) { return true; }
    return false;
}
'
' THIS SAMPLE CODE IS INCORRECT.
' DO NOT USE IT IN PRODUCTION.
'
Function ContainsHtmlSensitiveCharacters(input As String) As Boolean
    If input.IndexOf("<") >= 0 Then Return True
    If input.IndexOf("&") >= 0 Then Return True
    Return False
End Function

string.IndexOf(string) Vzhledem k tomu, že metoda používá lingvistické vyhledávání ve výchozím nastavení, je možné, že řetězec obsahuje znak literálu '<' nebo '&' a string.IndexOf(string) vrátit -1, což znamená, že podřetězec hledání nebyl nalezen. Pravidla analýzy kódu CA1307 a CA1309 označují taková místa volání a upozorňují vývojáře na potenciální problém.

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 jde o nejdůležitější operaci pro interpretaci řetězců, všechny instance volání těchto metod by měly být zkoumány, aby bylo možné určit, zda řetězce mají být interpretovány podle aktuální kultury, nebo mají být od kultury symbolicky odděleny. Obvykle se jedná o posledně jmenovanou možnost a místo toho by se mělo použít porovnání StringComparison.Ordinal.

Třída System.Globalization.CompareInfo, vrácená vlastností CultureInfo.CompareInfo, zahrnuje také metodu Compare, která poskytuje velký počet odpovídajících kritérií (řazení podle pořadí, ignorování prázdného místa, ignorování typu kana atd.) pomocí výčtu příznaků CompareOptions.

String.CompareTo

Výchozí interpretace: StringComparison.CurrentCulture.

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

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

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 testovat rovnost voláním statických nebo instančních přetížení metod 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 typ StringComparison, i pokud chcete provést řadové porovnání; to usnadňuje vyhledávání určité interpretace řetězců v kódu.

String.ToUpper a String.ToLower

Výchozí interpretace: StringComparison.CurrentCulture.

Při použití metod String.ToUpper() a String.ToLower() buďte opatrní, protože převod řetězce na velká nebo malá písmena se často používá jako dílčí normalizace pro porovnávání řetězců bez ohledu na velikost písmen. Pokud ano, zvažte použití porovnání ignorujícího velikost písmen.

K dispozici jsou také metody String.ToUpperInvariant a String.ToLowerInvariant. ToUpperInvariant je standardní způsob normalizace případů. Porovnání provedená pomocí StringComparison.OrdinalIgnoreCase mají podobu složení dvou volání: zavoláním ToUpperInvariant na oba řetězcové argumenty a porovnáním pomocí StringComparison.Ordinal.

Přetížení jsou také k dispozici pro převod na velká a malá písmena v konkrétní kultuře tím, že metodě předáte objekt CultureInfo, který tuto kulturu představuje.

Char.ToUpper a Char.ToLower

Výchozí interpretace: StringComparison.CurrentCulture.

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

String.StartsWith a String.EndsWith

Výchozí interpretace: StringComparison.CurrentCulture (pokud je prvním parametrem string) nebo StringComparison.Ordinal (pokud je prvním parametrem char).

Výchozí přetížení těchto metod nejsou konzistentní v tom, jak provádějí porovnání. Přetížení, která přijímají char parametr, provádějí ordinační porovnání, ale přetížení, která přijímají string parametr, provádějí porovnání citlivé na kulturu a mohou ignorovat netisknutelné 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 metody String.IndexOf a String.LastIndexOf, které zahrnují parametr Char, provádějí porovnání řadových hodnot, ale výchozí String.IndexOf a String.LastIndexOf metody, které obsahují parametr String, provádějí porovnání citlivé na jazykovou verzi.

Pokud zavoláte metodu String.IndexOf(String) nebo String.LastIndexOf(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 typ StringComparison. Přetížení zahrnující argument Char vám neumožňují zadat typ StringComparison.

String.Contains

Výchozí interpretace: StringComparison.Ordinal.

Na rozdíl od String.IndexOf, String.Contains metoda používá řadové porovnání ve výchozím nastavení pro obě char a string přetížení. Přesto byste měli předat explicitní StringComparison argument, pokud je záměr důležitý, aby bylo chování na místě volání jasné.

MemoryExtensions.AsSpan.IndexOfAny a typ SearchValues<T>

.NET 8 zavedl typ SearchValues<T> , který poskytuje optimalizované řešení pro vyhledávání konkrétních sad znaků nebo bajtů v rámci rozsahů.

Pokud porovnáváte řetězec s pevnou sadou známých hodnot opakovaně, zvažte použití SearchValues<T>.Contains(T) metody místo zřetězených porovnání nebo přístupů založených na LINQ. SearchValues<T> může předem zkompilovat interní vyhledávací struktury a optimalizovat logiku porovnání na základě zadaných hodnot. Pokud chcete zobrazit výhody výkonu, vytvořte instanci a položte ji SearchValues<string> do mezipaměti a pak ji znovu použijte pro porovnání:

using System.Buffers;

namespace ExampleCode;

internal partial class DemoCode
{
    private static readonly SearchValues<string> Commands =
        SearchValues.Create(
            ["start", "run", "go", "begin", "commence"],
            StringComparison.OrdinalIgnoreCase);

    void ProcessCommand(string command)
    {
        if (Commands.Contains(command))
        {
            // ...
        }
    }
}
Imports System.Buffers

Namespace ExampleCode
    Partial Friend Class DemoCode

        Private Shared ReadOnly Commands As SearchValues(Of String) =
            SearchValues.Create(
                {"start", "run", "go", "begin", "commence"},
                StringComparison.OrdinalIgnoreCase)

        Sub ProcessCommand(command As String)
            If Commands.Contains(command) Then
                ' ...
            End If
        End Sub

    End Class
End Namespace

V .NET 9 SearchValues jsme rozšířili podporu hledání podřetězců ve větším řetězci. Příklad najdete v SearchValues rozšíření.

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í typ StringComparer. 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 do kolekce nebo při čtení uložených dat ze souboru či databáze do kolekce může přepnutí aktuální jazykové verze narušit platnost invariantů 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ý řetězcový prvek v poli, metoda Array.Sort volá metodu String.Compare k uspořádání 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, kdy je pole seřazeno, a časem, kdy se prohledává jeho obsah. Například v následujícím kódu se ukládání a načítání provádí s porovnávačem, který je implicitně určen vlastností Thread.CurrentThread.CurrentCulture. Pokud se může kultura změnit mezi voláními StoreNames a DoesNameExist, a zejména pokud je obsah pole uložen někde mezi voláními těchto dvou metod, 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 projeví v řá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 uložena a přenesena mezi kulturami a řazení se používá k prezentaci těchto dat uživateli, můžete zvážit použití StringComparison.InvariantCulture, která funguje lingvisticky pro lepší uživatelský výstup, ale není ovlivněno změnami v kultuře. Následující příklad upravuje dva předchozí příklady tak, aby používaly invariantní jazykovou verzi pro řazení a hledání v poli.

// 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í: Hashtable konstruktor

Hashování řetězců představuje druhý příklad operace, která je ovlivněna způsobem porovnání řetězců.

Následující příklad vytvoří instanci objektu Hashtable předáním objektu StringComparer, který vrací vlastnost StringComparer.OrdinalIgnoreCase. Vzhledem k tomu, že třída StringComparer odvozená z StringComparer implementuje 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

Příklad kolekcí: SortedSet<T> a List<T>.Sort

Stejný problém místní citlivosti platí při vytváření instance seřazené kolekce řetězců nebo řazení existující kolekce na základě řetězců. Vždy zadejte explicitní porovnávač:

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

//
// Potentially incorrect code - behavior might vary based on locale.
//
SortedSet<string> mySet = [.. values]; // No comparer specified

List<string> list = [.. values];
list.Sort(); // No comparer specified

//
// Corrected code - uses ordinal sorting; doesn't vary by locale.
//
SortedSet<string> mySet2 = new(values, StringComparer.Ordinal);

List<string> list2 = [.. values];
list2.Sort(StringComparer.Ordinal);
' Words to sort
Dim values As String() = {"able", "ångström", "apple", "Æble",
                          "Windows", "Visual Studio"}

'
' Potentially incorrect code - behavior might vary based on locale.
'
Dim mySet As New SortedSet(Of String)(values) ' No comparer specified

Dim list As New List(Of String)(values)
list.Sort() ' No comparer specified

'
' Corrected code - uses ordinal sorting; doesn't vary by locale.
'
Dim mySet2 As New SortedSet(Of String)(values, StringComparer.Ordinal)

Dim list2 As New List(Of String)(values)
list2.Sort(StringComparer.Ordinal)

Rozdíly mezi rozhraním .NET a rozhraním .NET Framework

.NET a .NET Framework zpracovávají globalizaci odlišně. Rozhraní .NET Framework ve Windows používá pro porovnání lingvistických řetězců zařízení národní jazykové podpory (NLS) operačního systému. .NET používá knihovnu International Components for Unicode (ICU) pro porovnání lingvistických řetězců na všech podporovaných platformách.

Vzhledem k tomu, že ICU a NLS implementují různé logiky v lingvistických porovnáních, můžou se výsledky řetězcových metod, které používají porovnání citlivé na jazykovou verzi, lišit mezi rozhraním .NET a .NET Framework. Záleží na tom, která metoda ve výchozím nastavení používá lingvistický porovnávač, včetně:

Poznámka:

Toto není vyčerpávající seznam ovlivněných rozhraní API.

Jedním z znaménných rozdílů je zpracování vložených znaků null a dalších řídicích znaků. Při použití lingvistického porovnávače ve službě NLS mohou být některé řídicí znaky, jako je znak null (\0), považovány za ignorovatelné v určitých kontextech porovnání. V rámci ICU jsou tyto znaky interpretovány jako skutečné znaky v řetězci. To může způsobit, že string.IndexOf(string) vrátí různé výsledky, pokud hledaný řetězec obsahuje nulový znak.

Následující kód může například vytvořit jinou odpověď v závislosti na aktuálním modulu runtime:

const string greeting = "Hel\0lo";
Console.WriteLine($"{greeting.IndexOf("\0")}");

// The snippet prints:
//
// '3' when running on .NET Framework and .NET Core 2.x - 3.x (Windows)
// '0' when running on .NET 5 or later (Windows)
// '0' when running on .NET Core 2.x - 3.x or .NET 5 (non-Windows)
// '3' when running on .NET Core 2.x or .NET 5+ (in invariant mode)
Const greeting As String = "Hel" & vbNullChar & "lo"
Console.WriteLine($"{greeting.IndexOf(CStr(vbNullChar))}")

' The snippet prints:
'
' '3' when running on .NET Framework and .NET Core 2.x - 3.x (Windows)
' '0' when running on .NET 5 or later (Windows)
' '0' when running on .NET Core 2.x - 3.x or .NET 5 (non-Windows)
' '3' when running on .NET Core 2.x or .NET 5+ (in invariant mode)

Nejlepším způsobem, jak se těmto překvapením pro různé platformy a křížové implementace vyhnout, je vždy předat explicitní StringComparison argument metodám porovnání řetězců a použít StringComparison.Ordinal nebo StringComparison.OrdinalIgnoreCase pro nejazyčná porovnání.

Pokud migrujete aplikaci z rozhraní .NET Framework do .NET a spoléháte na starší chování služby NLS ve Windows, můžete aplikaci nakonfigurovat tak, aby používala službu NLS. Další informace najdete v tématu globalizace .NET a ICU.

Viz také