Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
.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.
- Použijte přetížení, která pro řetězcové operace explicitně určují pravidla porovnání řetězců. Obvykle to zahrnuje zavolání přetížené metody s parametrem typu StringComparison.
- Pro porovnání použijte StringComparison.Ordinal nebo StringComparison.OrdinalIgnoreCase jako bezpečný výchozí bod pro srovnávání řetězců, které jsou kulturně nezávislé.
- Porovnání s StringComparison.Ordinal nebo StringComparison.OrdinalIgnoreCase použijte k lepšímu výkonu.
- Při zobrazení výstupu uživateli použijte řetězcové operace založené na StringComparison.CurrentCulture.
- Místo řetězcových operací založených na StringComparison.Ordinal použijte nejazyčné StringComparison.OrdinalIgnoreCase nebo CultureInfo.InvariantCulture hodnoty, pokud je porovnání lingvisticky irelevantní (například symbolické).
- Při normalizaci řetězců pro porovnání použijte metodu String.ToUpperInvariant místo metody String.ToLowerInvariant.
- Pomocí přetížení String.Equals metody otestujte, zda jsou dva řetězce stejné.
- K řazení řetězců použijte String.Compare a String.CompareTo metody, nikoli ke kontrole rovnosti.
- Formátování citlivé na jazykovou verzi slouží k zobrazení neřetězných dat, jako jsou čísla a kalendářní data, v uživatelském rozhraní. Použijte formátování s invariantní kulturou k zachování neřetězcových dat ve formě řetězce.
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é.
Jasné 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ýč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í:
- IndexOf(Char), IndexOf(Char, Int32)a IndexOf(Char, Int32, Int32), které ve výchozím nastavení provádějí pořadové vyhledávání, při kterém je rozlišována velikost písmen a není brán ohled na jazykovou verzi, pro znak v řetězci.
- IndexOf(String), IndexOf(String, Int32)a IndexOf(String, Int32, Int32), která ve výchozím nastavení provádí hledání podřetězců v řetězci s rozlišováním velikosti písmen a kulturní citlivosti.
- IndexOf(String, StringComparison), IndexOf(String, Int32, StringComparison)a IndexOf(String, Int32, Int32, StringComparison), které zahrnují parametr typu StringComparison, který umožňuje zadat formulář porovnání.
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.Schemea "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. Pro více informací viz Řetězce a standard Unicode.
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:
- String.Compare přetížení, která neobsahují parametr StringComparison.
- String.CompareTo se přetěžuje.
- Výchozí metoda String.StartsWith(String) a metoda String.StartsWith(String, Boolean, CultureInfo) s parametrem
nullCultureInfo. - Výchozí metoda String.EndsWith(String) a metoda String.EndsWith(String, Boolean, CultureInfo) s parametrem
nullCultureInfo. - String.IndexOf přetížení, která přijímají String jako vyhledávací parametr a nemají parametr StringComparison.
- String.LastIndexOf přetížení, která přijímají String jako vyhledávací parametr a nemají parametr StringComparison.
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.
Ř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.
Ř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.
Volba člena StringComparison pro volání metody
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 |
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.
Ve výchozím nastavení obě tyto metody provádějí porovnání citlivé na kulturu. 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 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.
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ů:
- Porovnání řetězců citlivých na jazykovou verzi pomocí aktuální jazykové verze Tento objekt StringComparer je vrácen vlastností StringComparer.CurrentCulture.
- Porovnání nerozlišující malá a velká písmena pomocí aktuální kultury. Tento objekt StringComparer je vrácen vlastností StringComparer.CurrentCultureIgnoreCase.
- Porovnání nezávislé na kultuře za použití pravidel porovnání slov invariantní kultury. Tento objekt StringComparer je vrácen vlastností StringComparer.InvariantCulture.
- Porovnávání nerozlišující malá a velká písmena a kulturní rozdíly pomocí pravidel pro porovnávání slov invariantní kultury. Tento objekt StringComparer je vrácen vlastností StringComparer.InvariantCultureIgnoreCase.
- Řadové porovnání. Tento objekt StringComparer je vrácen vlastností StringComparer.Ordinal.
- Případová insenzitivní pořadová porovnání. Tento objekt StringComparer je vrácen vlastností StringComparer.OrdinalIgnoreCase.
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í: Konstruktor Hashtable
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