Osvědčené postupy pro porovnávání řetězců v .NET
.NET poskytuje rozsáhlou podporu pro vývoj lokalizovaných a globalizovaných aplikací a usnadňuje použití konvencí aktuální jazykové verze nebo konkrétní jazykové verze při provádění běžných operací, jako je řazení a zobrazování řetězců. Řazení nebo porovnávání řetězců ale není vždy operace citlivá na jazykovou verzi. Například řetězce, které aplikace používá interně, by se obvykle měly zpracovávat stejně napříč všemi jazykovými verzemi. Pokud jsou data řetězců nezávislá na kultuře, jako jsou značky XML, značky HTML, uživatelská jména, cesty k souborům a názvy systémových objektů, interpretována, jako by se jednalo o jazykovou verzi, kód aplikace může podléhat drobným chybám, špatnému výkonu a v některých případech problémům se zabezpečením.
Tento článek zkoumá metody řazení, porovnávání a velikostí písmen v .NET, obsahuje doporučení pro výběr vhodné metody zpracování řetězců a poskytuje další informace o metodách zpracování řetězců.
Doporučení pro použití řetězců
Při vývoji pomocí .NET při porovnávání řetězců postupujte podle těchto doporučení.
Tip
Porovnání provádí různé metody související s řetězci. Mezi příklady patří String.Equals, String.Compare, String.IndexOf, a String.StartsWith.
- Použijte přetížení, která explicitně určují pravidla porovnání řetězců pro řetězcové operace. Obvykle to zahrnuje volání přetížení metody, která má parametr typu StringComparison.
- Jako bezpečné výchozí nastavení pro porovnávání řetězců, které jsou nezávislé na jazykové verzi, použijte StringComparison.Ordinal nebo StringComparison.OrdinalIgnoreCase pro porovnání.
- Používejte porovnání s výkonem StringComparison.Ordinal nebo StringComparison.OrdinalIgnoreCase k lepšímu výkonu.
- Použijte řetězcové operace, které jsou založené na StringComparison.CurrentCulture zobrazení výstupu uživateli.
- Místo řetězcových operací použijte nejazyčné StringComparison.Ordinal nebo StringComparison.OrdinalIgnoreCase hodnoty založené na CultureInfo.InvariantCulture tom, kdy je porovnání lingvisticky irelevantní (například symbolické).
- Tuto metodu String.ToUpperInvariantString.ToLowerInvariant použijte místo metody při normalizaci řetězců pro porovnání.
- Pomocí přetížení String.Equals metody otestujte, zda jsou dva řetězce stejné.
- String.CompareString.CompareTo K řazení řetězců použijte metody, ne 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í. Formátování s invariantní jazykovou verzí slouží k zachování neřetězcových dat ve formuláři ř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 StringComparison.InvariantCulture . Jednou z několika výjimek je, když budete uchovávat lingvisticky smysluplná, ale kulturně nezávislá data.
- Nepoužívejte přetížení nebo CompareTo metodu String.Compare a otestujte návratovou hodnotu nuly, abyste zjistili, zda jsou dva řetězce stejné.
Explicitní zadání porovnání řetězců
Většina metod manipulace s řetězci v .NET je přetížená. Jedno nebo více přetížení obvykle přijímají výchozí nastavení, zatímco ostatní přijímají žádné výchozí hodnoty a místo toho definují přesný způsob, jakým se mají řetězce porovnávat nebo manipulovat. Většina metod, které nespoléhá na výchozí hodnoty, zahrnují parametr typu StringComparison, což je výčet, který explicitně určuje pravidla pro porovnání řetězců podle jazykové verze a případu. Následující tabulka popisuje členy výčtu StringComparison .
StringComparison – člen | Popis |
---|---|
CurrentCulture | Provede porovnání s rozlišováním velkých a malých písmen pomocí aktuální jazykové verze. |
CurrentCultureIgnoreCase | Provede porovnání nerozlišující malá a velká písmena pomocí aktuální jazykové verze. |
InvariantCulture | Provede porovnání s rozlišováním velkých a malých písmen pomocí neutrální jazykové verze. |
InvariantCultureIgnoreCase | Provede porovnání bez rozlišování velkých a malých písmen pomocí invariantní jazykové verze. |
Ordinal | Provede řadové porovnání. |
OrdinalIgnoreCase | Provádí porovnávání bez rozlišování malých a velkých písmen. |
Například IndexOf metoda, která vrací index podřetězce v objektu String , který odpovídá znaku nebo řetězci, má devět přetížení:
- IndexOf(Char), IndexOf(Char, Int32)a IndexOf(Char, Int32, Int32), které ve výchozím nastavení provádí řadové vyhledávání znaku v řetězci (rozlišující malá a malá písmena a jazyková verze).
- IndexOf(String), IndexOf(String, Int32)a IndexOf(String, Int32, Int32), které ve výchozím nastavení provádí hledání podřetězce v řetězci s rozlišováním velkých a malých písmen a jazykové verze.
- IndexOf(String, StringComparison), IndexOf(String, Int32, StringComparison)a IndexOf(String, Int32, Int32, StringComparison), které zahrnují parametr typu StringComparison , který umožňuje zadat formu 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 (ty, které hledají Char v instanci řetězce) provádějí řadové porovnání, zatímco jiné (ty, které hledají řetězec v instanci řetězce) jsou citlivé na jazykovou verzi. Je obtížné si zapamatovat, která metoda používá, která výchozí hodnota, a snadno zaměňovat přetížení.
Záměr kódu, který spoléhá na výchozí hodnoty volání metody, není jasný. V následujícím příkladu, který spoléhá na výchozí hodnoty, je obtížné zjistit, jestli vývojář skutečně zamýšlel pořadové nebo lingvistické porovnání dvou řetězců, nebo jestli rozdíl
url.Scheme
mezi písmeny a "https" může způsobit, že test rovnosti vrátífalse
.Uri url = new("https://learn.microsoft.com/"); // Incorrect if (string.Equals(url.Scheme, "https")) { // ...Code to handle HTTPS protocol. }
Dim url As New Uri("https://learn.microsoft.com/") ' Incorrect If String.Equals(url.Scheme, "https") Then ' ...Code to handle HTTPS protocol. End If
Obecně doporučujeme volat metodu, která nespoléhá na výchozí hodnoty, protože je záměr kódu jednoznačný. Díky tomu je kód čitelnější a snadněji se ladí a udržuje. Následující příklad řeší otázky vyvolané v předchozím příkladu. Je zřejmé, že se používá řadové porovnání a že rozdíly v případě ignorovány.
Uri url = new("https://learn.microsoft.com/");
// Correct
if (string.Equals(url.Scheme, "https", StringComparison.OrdinalIgnoreCase))
{
// ...Code to handle HTTPS protocol.
}
Dim url As New Uri("https://learn.microsoft.com/")
' Incorrect
If String.Equals(url.Scheme, "https", StringComparison.OrdinalIgnoreCase) Then
' ...Code to handle HTTPS protocol.
End If
Podrobnosti o porovnání řetězců
Porovnání řetězců je jádrem mnoha operací souvisejících s řetězci, zejména řazení a testování rovnosti. Řetězce se seřadí v určeném pořadí: Pokud se "my" zobrazí před "řetězec" v seřazených návazcích, musí "my" porovnat menší nebo rovno "řetězec". Porovnání navíc implicitně definuje rovnost. Operace porovnání vrátí nula pro řetězce, které považuje za rovno. Dobrou interpretací je, že ani jeden řetězec není menší než druhý. Nejvýstižnější operace zahrnující řetězce zahrnují jeden nebo oba tyto postupy: porovnávání s jiným řetězcem a provádění dobře definované operace řazení.
Poznámka:
Můžete si stáhnout tabulky hmotnosti řazení, sadu textových souborů, které obsahují informace o váhách znaků používaných při řazení a porovnání operací pro operační systémy Windows, a tabulku výchozích prvků kolace Unicode, nejnovější verzi tabulky hmotnosti řazení pro Linux a macOS. Konkrétní verze tabulky hmotnosti řazení v Systému Linux a macOS závisí na verzi knihoven International Components for Unicode nainstalovaných v systému. Informace o verzích ICU a verzích Unicode, které implementují, najdete v tématu Stažení ICU.
Vyhodnocení dvou řetězců pro rovnost nebo pořadí řazení však nepřináší jediný, správný výsledek; výsledek závisí na kritériích použitých k porovnání řetězců. Konkrétně porovnání řetězců, která jsou řadová nebo která jsou založená na konvencích dělení a řazení aktuální jazykové verze nebo invariantní jazykové verze (jazyková verze nezávislá na národním prostředí na základě anglického jazyka), mohou vést k různým výsledkům.
Kromě toho porovnání řetězců s různými verzemi .NET nebo .NET v různých operačních systémech nebo verzích operačního systému můžou vracet různé výsledky. Další informace naleznete v tématu Řetězce a Standard Unicode.
Porovnání řetězců, která používají aktuální jazykovou verzi
Jedním z kritérií je použití konvencí aktuální jazykové verze při porovnávání řetězců. Porovnání založená na aktuální jazykové verzi používají aktuální jazykovou verzi nebo národní prostředí vlákna. Pokud uživatel jazykovou verzi nenastaví, nastaví se jako výchozí nastavení operačního systému. Vždy byste měli použít porovnání založená na aktuální jazykové verzi, pokud jsou data lingvisticky relevantní, a když odráží interakci uživatele citlivé na jazykovou verzi.
Při změně jazykové verze se však změní chování porovnání a velikosti v .NET. K tomu dochází, když se aplikace spustí v počítači, který má jinou jazykovou verzi než počítač, na kterém byla aplikace vyvinuta, nebo když provádění vlákna změní jeho jazykovou verzi. Toto chování je záměrné, ale pro mnoho vývojářů to není zřejmé. Následující příklad znázorňuje rozdíly v pořadí řazení mezi jazykovými verzemi USA ("en-US") a švédštiny ("sv-SE"). Všimněte si, že slova "ångström", "Windows" a "Visual Studio" se zobrazují v různých pozicích v maticích seřazených řetězců.
using System.Globalization;
// Words to sort
string[] values= { "able", "ångström", "apple", "Æble",
"Windows", "Visual Studio" };
// Current culture
Array.Sort(values);
DisplayArray(values);
// Change culture to Swedish (Sweden)
string originalCulture = CultureInfo.CurrentCulture.Name;
Thread.CurrentThread.CurrentCulture = new CultureInfo("sv-SE");
Array.Sort(values);
DisplayArray(values);
// Restore the original culture
Thread.CurrentThread.CurrentCulture = new CultureInfo(originalCulture);
static void DisplayArray(string[] values)
{
Console.WriteLine($"Sorting using the {CultureInfo.CurrentCulture.Name} culture:");
foreach (string value in values)
Console.WriteLine($" {value}");
Console.WriteLine();
}
// The example displays the following output:
// Sorting using the en-US culture:
// able
// Æble
// ångström
// apple
// Visual Studio
// Windows
//
// Sorting using the sv-SE culture:
// able
// apple
// Visual Studio
// Windows
// ångström
// Æble
Imports System.Globalization
Imports System.Threading
Module Program
Sub Main()
' Words to sort
Dim values As String() = {"able", "ångström", "apple", "Æble",
"Windows", "Visual Studio"}
' Current culture
Array.Sort(values)
DisplayArray(values)
' Change culture to Swedish (Sweden)
Dim originalCulture As String = CultureInfo.CurrentCulture.Name
Thread.CurrentThread.CurrentCulture = New CultureInfo("sv-SE")
Array.Sort(values)
DisplayArray(values)
' Restore the original culture
Thread.CurrentThread.CurrentCulture = New CultureInfo(originalCulture)
End Sub
Sub DisplayArray(values As String())
Console.WriteLine($"Sorting using the {CultureInfo.CurrentCulture.Name} culture:")
For Each value As String In values
Console.WriteLine($" {value}")
Next
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' Sorting using the en-US culture:
' able
' Æble
' ångström
' apple
' Visual Studio
' Windows
'
' Sorting using the sv-SE culture:
' able
' apple
' Visual Studio
' Windows
' ångström
' Æble
Porovnání nerozlišující malá a velká písmena, která používají aktuální jazykovou verzi, jsou stejná jako porovnání citlivá na jazykovou verzi, s tím rozdílem, že ignorují malá a velká písmena podle aktuální jazykové verze vlákna. Toto chování se může projevit také v pořadí řazení.
Porovnání, která používají sémantiku aktuální jazykové verze, jsou výchozí pro následující metody:
- String.Compare přetížení, která neobsahují StringComparison parametr.
- String.CompareTo Přetížení.
- Výchozí String.StartsWith(String) metoda a String.StartsWith(String, Boolean, CultureInfo) metoda s parametrem
null
CultureInfo . - Výchozí String.EndsWith(String) metoda a String.EndsWith(String, Boolean, CultureInfo) metoda s parametrem
null
CultureInfo . - String.IndexOf přetížení, která přijímají String parametr hledání a nemají StringComparison parametr.
- String.LastIndexOf přetížení, která přijímají String parametr hledání a nemají StringComparison parametr.
V každém případě doporučujeme volat přetížení, které má StringComparison parametr, aby byl záměr volání metody jasný.
Drobné a ne tak drobné chyby se mohou objevit, když se jazykově interpretují nejazyčná řetězcová data nebo když se řetězcová data z konkrétní jazykové verze interpretují pomocí konvencí jiné jazykové verze. Kanonický příklad je turecko-I problém.
Pro téměř všechny latinky, včetně angličtiny v USA, je znak "i" (\u0069) malými písmeny znaku "I" (\u0049). Toto pravidlo casingu se rychle stane výchozím nastavením pro někoho, kdo programuje v takové jazykové verzi. Abeceda turečtina ("tr-TR") však obsahuje znak "I s tečkou" (\u0130), což je hlavní verze "i". Turečtina obsahuje také malá písmena "i bez tečky", "ı" (\u0131), která má velká písmena na "I". K tomuto chování dochází také v ázerbájdžánské kultuře ("az").
Proto předpoklady týkající se velkých písmen "i" nebo nižších písmen "I" nejsou platné mezi všemi kulturami. Pokud použijete výchozí přetížení pro rutiny porovnání řetězců, budou podléhat rozptylu mezi jazykovými verzemi. Pokud porovnávaná data nejsou jazyková, může použití výchozích přetížení vést k nežádoucím výsledkům, protože následující pokus o provedení porovnání řetězců bill a BILL nerozlišuje malá a velká písmena.
using System.Globalization;
string name = "Bill";
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}");
Console.WriteLine($" Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}");
Console.WriteLine($" Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", true, null)}");
Console.WriteLine();
Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}");
Console.WriteLine($" Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}");
Console.WriteLine($" Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", true, null)}");
//' The example displays the following output:
//'
//' Culture = English (United States)
//' Is 'Bill' the same as 'BILL'? True
//' Does 'Bill' start with 'BILL'? True
//'
//' Culture = Turkish (Türkiye)
//' Is 'Bill' the same as 'BILL'? True
//' Does 'Bill' start with 'BILL'? False
Imports System.Globalization
Imports System.Threading
Module Program
Sub Main()
Dim name As String = "Bill"
Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}")
Console.WriteLine($" Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}")
Console.WriteLine($" Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", True, Nothing)}")
Console.WriteLine()
Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}")
Console.WriteLine($" Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}")
Console.WriteLine($" Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", True, Nothing)}")
End Sub
End Module
' The example displays the following output:
'
' Culture = English (United States)
' Is 'Bill' the same as 'BILL'? True
' Does 'Bill' start with 'BILL'? True
'
' Culture = Turkish (Türkiye)
' Is 'Bill' the same as 'BILL'? True
' Does 'Bill' start with 'BILL'? False
Toto porovnání může způsobit významné problémy, pokud je jazyková verze neúmyslně používána v nastavení citlivém na zabezpečení, jako v následujícím příkladu. Volání metody, jako IsFileURI("file:")
je například vrácení true
, pokud je aktuální jazyková verze angličtina USA, ale false
pokud je aktuální jazyková verze turečtina. V tureckém systému by tedy někdo mohl obejít bezpečnostní opatření, která blokují přístup k identifikátorům URI bez rozlišování malých a malých písmen, které začínají na "FILE:".
public static bool IsFileURI(string path) =>
path.StartsWith("FILE:", true, null);
Public Shared Function IsFileURI(path As String) As Boolean
Return path.StartsWith("FILE:", True, Nothing)
End Function
V tomto případě, protože "file:" je určen k interpretaci jako nejazyčný identifikátor nerozlišující jazykovou verzi, měl by být kód napsán, jak je znázorněno v následujícím příkladu:
public static bool IsFileURI(string path) =>
path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
Public Shared Function IsFileURI(path As String) As Boolean
Return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase)
End Function
Operace řadových řetězců
Určení StringComparison.Ordinal nebo StringComparison.OrdinalIgnoreCase hodnoty ve volání metody označuje nejazyčné porovnání, ve kterém jsou vlastnosti přirozeného jazyka ignorovány. Metody, které jsou vyvolány s těmito StringComparison hodnotami základní řetězcové operace rozhodnutí o jednoduchých porovnávání bajtů místo dělení písmen nebo ekvivalence tabulek, které jsou parametrizovány jazykovou verzí. Ve většině případů je tento přístup nejvhodnější pro zamýšlenou interpretaci řetězců a zároveň rychlejší a spolehlivější kód.
Řadové porovnání jsou porovnání řetězců, ve kterých se každý bajt každého řetězce porovnává bez jazykové interpretace; Například "windows" neodpovídá "Windows". Jedná se v podstatě o volání funkce modulu runtime strcmp
jazyka C. Toto porovnání použijte, když kontext určuje, že řetězce by se měly přesně shodovat nebo vyžadují konzervativní odpovídající zásady. Kromě toho je pořadové porovnání nejrychlejší operací porovnání, protože při určování výsledku nepoužívá žádná jazyková pravidla.
Řetězce v .NET můžou obsahovat vložené znaky null (a další netiskné znaky). Jedním z nejjasnějších rozdílů mezi řadovým a jazykovým porovnáním (včetně porovnání, která používají invariantní jazykovou verzi) je zpracování vložených znaků null v řetězci. Tyto znaky jsou ignorovány při použití String.Compare a String.Equals metod k provádění porovnání citlivých na jazykovou verzi (včetně porovnání, která používají invariantní jazykovou verzi). Výsledkem je, že řetězce, které obsahují vložené znaky null, se dají považovat za řetězce, které ne. Vložené netisknutné znaky mohou být vynechány pro účely metod porovnání řetězců, například String.StartsWith.
Důležité
I když metody porovnání řetězců ignorují vložené znaky null, metody vyhledávání řetězců, jako String.Containsje , String.EndsWithString.IndexOf, String.LastIndexOfa String.StartsWith ne.
Následující příklad provádí porovnání řetězce "Aa" s podobným řetězcem, který obsahuje několik vložených znaků null mezi "A" a "a", a ukazuje, jak jsou tyto dva řetězce považovány za stejné:
string str1 = "Aa";
string str2 = "A" + new string('\u0000', 3) + "a";
Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo("en-us");
Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):");
Console.WriteLine(" With String.Compare:");
Console.WriteLine($" Current Culture: {string.Compare(str1, str2, StringComparison.CurrentCulture)}");
Console.WriteLine($" Invariant Culture: {string.Compare(str1, str2, StringComparison.InvariantCulture)}");
Console.WriteLine(" With String.Equals:");
Console.WriteLine($" Current Culture: {string.Equals(str1, str2, StringComparison.CurrentCulture)}");
Console.WriteLine($" Invariant Culture: {string.Equals(str1, str2, StringComparison.InvariantCulture)}");
string ShowBytes(string value)
{
string hexString = string.Empty;
for (int index = 0; index < value.Length; index++)
{
string result = Convert.ToInt32(value[index]).ToString("X4");
result = string.Concat(" ", result.Substring(0,2), " ", result.Substring(2, 2));
hexString += result;
}
return hexString.Trim();
}
// The example displays the following output:
// Comparing 'Aa' (00 41 00 61) and 'Aa' (00 41 00 00 00 00 00 00 00 61):
// With String.Compare:
// Current Culture: 0
// Invariant Culture: 0
// With String.Equals:
// Current Culture: True
// Invariant Culture: True
Module Program
Sub Main()
Dim str1 As String = "Aa"
Dim str2 As String = "A" & New String(Convert.ToChar(0), 3) & "a"
Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):")
Console.WriteLine(" With String.Compare:")
Console.WriteLine($" Current Culture: {String.Compare(str1, str2, StringComparison.CurrentCulture)}")
Console.WriteLine($" Invariant Culture: {String.Compare(str1, str2, StringComparison.InvariantCulture)}")
Console.WriteLine(" With String.Equals:")
Console.WriteLine($" Current Culture: {String.Equals(str1, str2, StringComparison.CurrentCulture)}")
Console.WriteLine($" Invariant Culture: {String.Equals(str1, str2, StringComparison.InvariantCulture)}")
End Sub
Function ShowBytes(str As String) As String
Dim hexString As String = String.Empty
For ctr As Integer = 0 To str.Length - 1
Dim result As String = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
result = String.Concat(" ", result.Substring(0, 2), " ", result.Substring(2, 2))
hexString &= result
Next
Return hexString.Trim()
End Function
' The example displays the following output:
' Comparing 'Aa' (00 41 00 61) and 'Aa' (00 41 00 00 00 00 00 00 00 61):
' With String.Compare:
' Current Culture: 0
' Invariant Culture: 0
' With String.Equals:
' Current Culture: True
' Invariant Culture: True
End Module
Při použití řadového porovnání se však řetězce nepovažují za stejné, jak ukazuje následující příklad:
string str1 = "Aa";
string str2 = "A" + new String('\u0000', 3) + "a";
Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):");
Console.WriteLine(" With String.Compare:");
Console.WriteLine($" Ordinal: {string.Compare(str1, str2, StringComparison.Ordinal)}");
Console.WriteLine(" With String.Equals:");
Console.WriteLine($" Ordinal: {string.Equals(str1, str2, StringComparison.Ordinal)}");
string ShowBytes(string str)
{
string hexString = string.Empty;
for (int ctr = 0; ctr < str.Length; ctr++)
{
string result = Convert.ToInt32(str[ctr]).ToString("X4");
result = " " + result.Substring(0, 2) + " " + result.Substring(2, 2);
hexString += result;
}
return hexString.Trim();
}
// The example displays the following output:
// Comparing 'Aa' (00 41 00 61) and 'A a' (00 41 00 00 00 00 00 00 00 61):
// With String.Compare:
// Ordinal: 97
// With String.Equals:
// Ordinal: False
Module Program
Sub Main()
Dim str1 As String = "Aa"
Dim str2 As String = "A" & New String(Convert.ToChar(0), 3) & "a"
Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):")
Console.WriteLine(" With String.Compare:")
Console.WriteLine($" Ordinal: {String.Compare(str1, str2, StringComparison.Ordinal)}")
Console.WriteLine(" With String.Equals:")
Console.WriteLine($" Ordinal: {String.Equals(str1, str2, StringComparison.Ordinal)}")
End Sub
Function ShowBytes(str As String) As String
Dim hexString As String = String.Empty
For ctr As Integer = 0 To str.Length - 1
Dim result As String = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
result = String.Concat(" ", result.Substring(0, 2), " ", result.Substring(2, 2))
hexString &= result
Next
Return hexString.Trim()
End Function
' The example displays the following output:
' Comparing 'Aa' (00 41 00 61) and 'A a' (00 41 00 00 00 00 00 00 00 61):
' With String.Compare:
' Ordinal: 97
' With String.Equals:
' Ordinal: False
End Module
Porovnání řadových řad nerozlišují malá a velká a malá písmena jsou dalším nejkonkonzervativnějším přístupem. Tato porovnání ignorují většinu případů; Například "windows" odpovídá "Windows". Při práci se znaky ASCII je tato zásada ekvivalentní StringComparison.Ordinal, s tím rozdílem, že ignoruje obvyklé písmena ASCII. Proto jakýkoli znak v [A, Z] (\u0041-\u005A) odpovídá odpovídajícímu znaku v [a,z] (\u0061-\007A). Velikost velikostí mimo rozsah ASCII používá invariantní tabulky jazykové verze. Proto následující porovnání:
string.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)
je ekvivalentní (ale rychlejší než) toto porovnání:
string.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal);
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal)
Tato porovnání jsou stále velmi rychlá.
Binární StringComparison.Ordinal hodnoty použijte StringComparison.OrdinalIgnoreCase přímo a nejlépe se hodí pro porovnávání. Pokud si nejste jistí nastavením porovnání, použijte jednu z těchto dvou hodnot. Vzhledem k tomu, že ale provádějí porovnání bajtů po bajtech, neřadí se podle lingvistického pořadí řazení (například anglického slovníku), ale podle binárního pořadí řazení. Výsledky můžou být ve většině kontextů liché, pokud se zobrazí uživatelům.
Ordinální sémantika je výchozím nastavením přetížení String.Equals , která nezahrnují StringComparison argument (včetně operátoru rovnosti). V každém případě doporučujeme volat přetížení, které má StringComparison parametr.
Řetězcové operace, které používají neutrální jazykovou verzi
Porovnání s invariantní jazykovou verzí používají CompareInfo vlastnost vrácenou statickou CultureInfo.InvariantCulture vlastností. Toto chování je stejné ve všech systémech; překládá všechny znaky mimo rozsah do toho, co se domnívá, že jsou ekvivalentní invariantní znaky. Tato zásada může být užitečná pro udržování jedné sady chování řetězců napříč jazykovými verzemi, ale často poskytuje neočekávané výsledky.
Porovnání nerozlišující malá a velká písmena s invariantní jazykovou verzí používají statickou CompareInfo vlastnost vrácenou statickou CultureInfo.InvariantCulture vlastností také pro informace o porovnání. Všechny rozdíly mezi těmito přeloženými znaky jsou ignorovány.
Porovnání, která používají StringComparison.InvariantCulture řetězce ASCII a StringComparison.Ordinal pracují shodně. Nicméně provede lingvistická rozhodnutí, StringComparison.InvariantCulture která nemusí být vhodná pro řetězce, které musí být interpretovány jako sada bajtů. Objekt CultureInfo.InvariantCulture.CompareInfo
vytvoří metodu Compare interpretovat určité sady znaků jako ekvivalentní. Například následující ekvivalence je platná v rámci invariantní jazykové verze:
InvariantCulture: a + ̊ = å
MALÉ PÍSMENO LATINKY A znak "a" (\u0061), je-li vedle znaku COMBINING RING NAD znakem "+ " ̊" (\u030a), interpretován jako MALÉ PÍSMENO LATINKY A SE znakem å (\u00e5). Jak ukazuje následující příklad, toto chování se liší od řadového porovnání.
string separated = "\u0061\u030a";
string combined = "\u00e5";
Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}",
separated, combined,
string.Compare(separated, combined, StringComparison.InvariantCulture) == 0);
Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}",
separated, combined,
string.Compare(separated, combined, StringComparison.Ordinal) == 0);
// The example displays the following output:
// Equal sort weight of a° and å using InvariantCulture: True
// Equal sort weight of a° and å using Ordinal: False
Module Program
Sub Main()
Dim separated As String = ChrW(&H61) & ChrW(&H30A)
Dim combined As String = ChrW(&HE5)
Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}",
separated, combined,
String.Compare(separated, combined, StringComparison.InvariantCulture) = 0)
Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}",
separated, combined,
String.Compare(separated, combined, StringComparison.Ordinal) = 0)
' The example displays the following output:
' Equal sort weight of a° and å using InvariantCulture: True
' Equal sort weight of a° and å using Ordinal: False
End Sub
End Module
Při interpretaci názvůsouborůchm souborům se může objevit při interpretaci názvů souborů, souborů cookie nebo čehokoli jiného, kde se může objevit kombinace, například "å"
Při vyvážení má invariantní jazyková verze několik vlastností, díky kterým je užitečné pro porovnání. Porovnává jazykově relevantním způsobem, což brání v zaručení úplné symbolické ekvivalence, ale není to volba pro zobrazení v žádné jazykové verzi. Jedním z několika důvodů, proč je použít StringComparison.InvariantCulture k porovnání, je zachovat seřazená data pro křížově identické zobrazení. Pokud například velký datový soubor, který obsahuje seznam seřazených identifikátorů pro zobrazení, doprovází aplikaci, přidání do tohoto seznamu by vyžadovalo vložení s neutrálním řazením ve stylu.
Volba člena StringComparison pro volání metody
Následující tabulka popisuje mapování z kontextu sémantického řetězce na člen výčtu StringComparison :
Data | Chování | Odpovídající System.StringComparison hodnota |
---|---|---|
Interní identifikátory citlivé na malá a velká písmena. Identifikátory rozlišující malá a velká písmena ve standardech, jako je XML a HTTP. Nastavení související se zabezpečením s rozlišováním velkých a malých písmen |
Nejazyčný identifikátor, kde se bajty přesně shodují. | Ordinal |
Interní identifikátory nerozlišující malá a velká písmena. Identifikátory nerozlišující malá a velká písmena ve standardech, jako je XML a HTTP. Cesty k souborům Klíče a hodnoty registru. Proměnné prostředí. Identifikátory prostředků (například popisovač názvů) Nastavení související se zabezpečením nerozlišují malá a velká písmena. |
Nejazyčný identifikátor, pokud je případ irelevantní. | OrdinalIgnoreCase |
Některá trvalá, lingvisticky relevantní data. Zobrazení lingvistických dat, která vyžadují pevné pořadí řazení |
Kulturní nezávislá data, která jsou stále lingvisticky relevantní. | InvariantCulture nebo InvariantCultureIgnoreCase |
Data zobrazená uživateli Většina uživatelských vstupů. |
Data, která vyžadují místní lingvistické celní údaje. | CurrentCulture nebo CurrentCultureIgnoreCase |
Běžné metody porovnání řetězců v .NET
Následující části popisují metody, které se nejčastěji používají pro porovnání řetězců.
String.compare
Výchozí interpretace: StringComparison.CurrentCulture.
Vzhledem k tomu, že operace nejvíce centrální pro interpretaci řetězců, všechny instance těchto volání metody by měly být zkoumány, aby bylo možné určit, zda řetězce mají být interpretovány podle aktuální jazykové verze, nebo se oddělí od jazykové verze (symbolicky). Obvykle se jedná o druhou a StringComparison.Ordinal místo toho by se mělo použít porovnání.
Třída System.Globalization.CompareInfo , která je vrácena CultureInfo.CompareInfo vlastností, zahrnuje také metodu Compare , která poskytuje velký počet odpovídajících možností (pořadové číslování, ignorování prázdných znaků, ignorování typu kana atd.) pomocí výčtu příznaku CompareOptions .
String.CompareTo
Výchozí interpretace: StringComparison.CurrentCulture.
Tato metoda v současné době nenabízí přetížení, které určuje StringComparison typ. Obvykle je možné tuto metodu převést na doporučený String.Compare(String, String, StringComparison) formulář.
Typy, které implementují tuto metodu IComparable a IComparable<T> rozhraní. Vzhledem k tomu, že nenabízí možnost parametru StringComparison , implementace typů často umožňuje uživateli zadat v jeho konstruktoru StringComparer . Následující příklad definuje FileName
třídu, jejíž konstruktor třídy obsahuje StringComparer parametr. Tento StringComparer objekt se pak použije v FileName.CompareTo
metodě.
class FileName : IComparable
{
private readonly StringComparer _comparer;
public string Name { get; }
public FileName(string name, StringComparer? comparer)
{
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
Name = name;
if (comparer != null)
_comparer = comparer;
else
_comparer = StringComparer.OrdinalIgnoreCase;
}
public int CompareTo(object? obj)
{
if (obj == null) return 1;
if (obj is not FileName)
return _comparer.Compare(Name, obj.ToString());
else
return _comparer.Compare(Name, ((FileName)obj).Name);
}
}
Class FileName
Implements IComparable
Private ReadOnly _comparer As StringComparer
Public ReadOnly Property Name As String
Public Sub New(name As String, comparer As StringComparer)
If (String.IsNullOrEmpty(name)) Then Throw New ArgumentNullException(NameOf(name))
Me.Name = name
If comparer IsNot Nothing Then
_comparer = comparer
Else
_comparer = StringComparer.OrdinalIgnoreCase
End If
End Sub
Public Function CompareTo(obj As Object) As Integer Implements IComparable.CompareTo
If obj Is Nothing Then Return 1
If TypeOf obj IsNot FileName Then
Return _comparer.Compare(Name, obj.ToString())
Else
Return _comparer.Compare(Name, DirectCast(obj, FileName).Name)
End If
End Function
End Class
String.Equals
Výchozí interpretace: StringComparison.Ordinal.
Třída String umožňuje otestovat rovnost voláním přetížení statické metody nebo metody instance Equals nebo pomocí statického operátoru rovnosti. Přetížení a operátor ve výchozím nastavení používají řadové porovnání. Přesto však doporučujeme volat přetížení, které explicitně určuje StringComparison typ, i když chcete provést řadové porovnání; to usnadňuje vyhledávání kódu pro určitou interpretaci řetězců.
String.ToUpper a String.ToLower
Výchozí interpretace: StringComparison.CurrentCulture.
Při použití String.ToUpper() a String.ToLower() metod buďte opatrní, protože vynucení řetězce na velká nebo malá písmena se často používá jako malá normalizace pro porovnávání řetězců bez ohledu na malá písmena. Pokud ano, zvažte použití porovnání bez rozlišování velkých a malých písmen.
String.ToLowerInvariant K String.ToUpperInvariant dispozici jsou také tyto metody. ToUpperInvariant je standardní způsob, jak normalizovat případ. Porovnání provedená pomocí StringComparison.OrdinalIgnoreCase jsou chování složení dvou volání: volání ToUpperInvariant obou řetězcových argumentů a porovnání pomocí StringComparison.Ordinal.
Přetížení jsou také k dispozici pro převod na velká a malá písmena v konkrétní jazykové verzi předáním CultureInfo objektu, který představuje danou jazykovou verzi metodě.
Char.ToUpper a Char.ToLower
Výchozí interpretace: StringComparison.CurrentCulture.
Metody Char.ToUpper(Char) a Char.ToLower(Char) metody fungují podobně String.ToUpper()String.ToLower() jako metody popsané v předchozí části.
String.StartsWith a String.EndsWith
Výchozí interpretace: StringComparison.CurrentCulture.
Ve výchozím nastavení obě tyto metody provádějí porovnání citlivé na jazykovou verzi. Konkrétně mohou ignorovat netisknutné znaky.
String.IndexOf a String.LastIndexOf
Výchozí interpretace: StringComparison.CurrentCulture.
Existuje nedostatek konzistence v tom, jak výchozí přetížení těchto metod provádí porovnání. Všechny String.IndexOf a String.LastIndexOf metody, které obsahují Char parametr, provádějí řadové porovnání, ale výchozí String.IndexOf a String.LastIndexOf metody, které obsahují String parametr, provádějí porovnání citlivé na jazykovou verzi.
Pokud zavoláte nebo String.LastIndexOf(String) metodu String.IndexOf(String) a předáte jí řetězec pro vyhledání v aktuální instanci, doporučujeme volat přetížení, které explicitně určuje StringComparison typ. Přetížení, která obsahují Char argument, neumožňují zadat StringComparison typ.
Metody, které provádějí porovnání řetězců nepřímo
Některé neřetězcové metody, které mají porovnání řetězců jako centrální operace, používají StringComparer typ. Třída StringComparer obsahuje šest statických vlastností, které vracejí StringComparer instance, jejichž StringComparer.Compare metody provádějí následující typy porovnání řetězců:
- Porovnání řetězců citlivých na jazykovou verzi pomocí aktuální jazykové verze Tento StringComparer objekt je vrácen StringComparer.CurrentCulture vlastností.
- Porovnání nerozlišující malá a velká písmena pomocí aktuální jazykové verze Tento StringComparer objekt je vrácen StringComparer.CurrentCultureIgnoreCase vlastností.
- Porovnání nerozlišující jazykovou verzi pomocí pravidel porovnání slov invariantní jazykové verze Tento StringComparer objekt je vrácen StringComparer.InvariantCulture vlastností.
- Porovnání nerozlišující malá a velká a malá písmena a nerozlišují jazykovou verzi pomocí pravidel porovnání slov invariantní jazykové verze. Tento StringComparer objekt je vrácen StringComparer.InvariantCultureIgnoreCase vlastností.
- Řadové porovnání. Tento StringComparer objekt je vrácen StringComparer.Ordinal vlastností.
- Porovnání řadových řad nerozlišují malá a velká písmena. Tento StringComparer objekt je vrácen StringComparer.OrdinalIgnoreCase vlastností.
Array.Sort a Array.BinarySearch
Výchozí interpretace: StringComparison.CurrentCulture.
Při ukládání jakýchkoli dat v kolekci nebo čtení trvalých dat ze souboru nebo databáze do kolekce může přepnutí aktuální jazykové verze zneplatnit invarianty v kolekci. Metoda Array.BinarySearch předpokládá, že prvky v poli, které mají být prohledány, jsou již seřazeny. Chcete-li seřadit libovolný prvek řetězce v poli, Array.Sort metoda volá metodu String.Compare pořadí jednotlivých prvků. Použití porovnávače citlivého na jazykovou verzi může být nebezpečné, pokud se jazyková verze změní mezi časem řazení pole a jeho obsahem. Například v následujícím kódu funguje úložiště a načítání s porovnávačem, který je implicitně poskytován vlastností Thread.CurrentThread.CurrentCulture
. Pokud se jazyková verze může mezi voláními a StoreNames
DoesNameExist
a , a zejména v případě, že obsah pole je trvalý někde mezi dvěma voláními metody, binární vyhledávání může selhat.
// Incorrect
string[] _storedNames;
public void StoreNames(string[] names)
{
_storedNames = new string[names.Length];
// Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length);
Array.Sort(_storedNames); // Line A
}
public bool DoesNameExist(string name) =>
Array.BinarySearch(_storedNames, name) >= 0; // Line B
' Incorrect
Dim _storedNames As String()
Sub StoreNames(names As String())
ReDim _storedNames(names.Length - 1)
' Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length)
Array.Sort(_storedNames) ' Line A
End Sub
Function DoesNameExist(name As String) As Boolean
Return Array.BinarySearch(_storedNames, name) >= 0 ' Line B
End Function
V následujícím příkladu se zobrazí doporučená varianta, která používá stejnou metodu porovnání bez rozlišení jazykové verze k seřazení i vyhledávání v poli. Kód změny se odráží na řádcích označených Line A
a Line B
v obou příkladech.
// Correct
string[] _storedNames;
public void StoreNames(string[] names)
{
_storedNames = new string[names.Length];
// Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length);
Array.Sort(_storedNames, StringComparer.Ordinal); // Line A
}
public bool DoesNameExist(string name) =>
Array.BinarySearch(_storedNames, name, StringComparer.Ordinal) >= 0; // Line B
' Correct
Dim _storedNames As String()
Sub StoreNames(names As String())
ReDim _storedNames(names.Length - 1)
' Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length)
Array.Sort(_storedNames, StringComparer.Ordinal) ' Line A
End Sub
Function DoesNameExist(name As String) As Boolean
Return Array.BinarySearch(_storedNames, name, StringComparer.Ordinal) >= 0 ' Line B
End Function
Pokud jsou tato data trvalá a přesunutá mezi jazykovými verzemi a řazení se používají k prezentování těchto dat uživateli, můžete zvážit použití StringComparison.InvariantCulture, které funguje v lingvisticky pro lepší výstup uživatele, ale změny v jazykové verzi nejsou ovlivněné. Následující příklad upraví dva předchozí příklady tak, aby používaly invariantní jazykovou verzi pro řazení a prohledávání pole.
// Correct
string[] _storedNames;
public void StoreNames(string[] names)
{
_storedNames = new string[names.Length];
// Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length);
Array.Sort(_storedNames, StringComparer.InvariantCulture); // Line A
}
public bool DoesNameExist(string name) =>
Array.BinarySearch(_storedNames, name, StringComparer.InvariantCulture) >= 0; // Line B
' Correct
Dim _storedNames As String()
Sub StoreNames(names As String())
ReDim _storedNames(names.Length - 1)
' Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length)
Array.Sort(_storedNames, StringComparer.InvariantCulture) ' Line A
End Sub
Function DoesNameExist(name As String) As Boolean
Return Array.BinarySearch(_storedNames, name, StringComparer.InvariantCulture) >= 0 ' Line B
End Function
Příklad kolekcí: Konstruktor hashtable
Řetězce hash poskytují druhý příklad operace, která je ovlivněna způsobem porovnání řetězců.
Následující příklad vytvoří Hashtable instanci objektu předáním objektu StringComparer , který je vrácen vlastností StringComparer.OrdinalIgnoreCase . Protože třída StringComparer , která je odvozena z StringComparer implements rozhraní IEqualityComparer , jeho GetHashCode metoda se používá k výpočtu hash kódu řetězců v tabulce hash.
using System.IO;
using System.Collections;
const int InitialCapacity = 100;
Hashtable creationTimeByFile = new(InitialCapacity, StringComparer.OrdinalIgnoreCase);
string directoryToProcess = Directory.GetCurrentDirectory();
// Fill the hash table
PopulateFileTable(directoryToProcess);
// Get some of the files and try to find them with upper cased names
foreach (var file in Directory.GetFiles(directoryToProcess))
PrintCreationTime(file.ToUpper());
void PopulateFileTable(string directory)
{
foreach (string file in Directory.GetFiles(directory))
creationTimeByFile.Add(file, File.GetCreationTime(file));
}
void PrintCreationTime(string targetFile)
{
object? dt = creationTimeByFile[targetFile];
if (dt is DateTime value)
Console.WriteLine($"File {targetFile} was created at time {value}.");
else
Console.WriteLine($"File {targetFile} does not exist.");
}
Imports System.IO
Module Program
Const InitialCapacity As Integer = 100
Private ReadOnly s_creationTimeByFile As New Hashtable(InitialCapacity, StringComparer.OrdinalIgnoreCase)
Private ReadOnly s_directoryToProcess As String = Directory.GetCurrentDirectory()
Sub Main()
' Fill the hash table
PopulateFileTable(s_directoryToProcess)
' Get some of the files and try to find them with upper cased names
For Each File As String In Directory.GetFiles(s_directoryToProcess)
PrintCreationTime(File.ToUpper())
Next
End Sub
Sub PopulateFileTable(directoryPath As String)
For Each file As String In Directory.GetFiles(directoryPath)
s_creationTimeByFile.Add(file, IO.File.GetCreationTime(file))
Next
End Sub
Sub PrintCreationTime(targetFile As String)
Dim dt As Object = s_creationTimeByFile(targetFile)
If TypeOf dt Is Date Then
Console.WriteLine($"File {targetFile} was created at time {DirectCast(dt, Date)}.")
Else
Console.WriteLine($"File {targetFile} does not exist.")
End If
End Sub
End Module