.NET中字符串比較的最佳用法
.NET 為開發(fā)本地化和全球化應(yīng)用程序提供廣泛支持,在執(zhí)行排序和顯示字符串等常見操作時(shí),輕松應(yīng)用當(dāng)前區(qū)域性或特定區(qū)域性的約定。 但排序或比較字符串并不總是區(qū)分區(qū)域性的操作。
例如,對(duì)于應(yīng)用程序內(nèi)部使用的字符串,通常應(yīng)該跨所有區(qū)域性以相同的方式對(duì)其進(jìn)行處理。 如果將 XML 標(biāo)記、HTML 標(biāo)記、用戶名、文件路徑和系統(tǒng)對(duì)象名稱等與區(qū)域性無(wú)關(guān)的字符串?dāng)?shù)據(jù)解釋為區(qū)分區(qū)域性,則應(yīng)用程序代碼會(huì)遭遇細(xì)微的錯(cuò)誤、不佳的性能,在某些情況下,還會(huì)遭遇安全性問(wèn)題。
本文介紹 .NET 中的字符串排序、比較和大小寫方法,針對(duì)如何選擇適當(dāng)?shù)淖址幚矸椒ㄌ岢鼋ㄗh,并提供有關(guān)字符串處理方法的其他信息。
對(duì)字符串用法的建議
使用 .NET 進(jìn)行開發(fā)時(shí),請(qǐng)遵循以下簡(jiǎn)要建議比較字符串:
- 使用為字符串操作顯式指定字符串比較規(guī)則的重載。 通常情況下,這涉及調(diào)用具有 StringComparison類型的參數(shù)的方法重載。
- 使用 StringComparison.Ordinal 或 StringComparison.OrdinalIgnoreCase 進(jìn)行比較,并以此作為匹配區(qū)域性不明確的字符串的安全默認(rèn)設(shè)置。
- 將比較與 StringComparison.Ordinal 或 StringComparison.OrdinalIgnoreCase 配合使用,以獲得更好的性能。
- 向用戶顯示輸出時(shí),使用基于 StringComparison.CurrentCulture 的字符串操作。
- 當(dāng)進(jìn)行與語(yǔ)言(例如,符號(hào))無(wú)關(guān)的比較時(shí),使用非語(yǔ)言的 StringComparison.Ordinal 或 StringComparison.OrdinalIgnoreCase 值,而不使用基于 CultureInfo.InvariantCulture 的字符串操作。
- 在規(guī)范化要比較的字符串時(shí),使用 String.ToUpperInvariant 方法而非 String.ToLowerInvariant 方法。
- 使用 String.Equals 方法的重載來(lái)測(cè)試兩個(gè)字符串是否相等。
- 使用 String.Compare 和 String.CompareTo 方法可對(duì)字符串進(jìn)行排序,而不是檢查字符串是否相等。
- 在用戶界面,使用區(qū)分區(qū)域性的格式顯示非字符串?dāng)?shù)據(jù),如數(shù)字和日期。 使用格式以固定區(qū)域性使非字符串?dāng)?shù)據(jù)顯示為字符串形式。
比較字符串時(shí),請(qǐng)避免采用以下做法:
- 不要使用未顯式或隱式為字符串操作指定字符串比較規(guī)則的重載。
- 在大多數(shù)情況下,不要使用基于 StringComparison.InvariantCulture 的字符串操作。 其中的一個(gè)少數(shù)例外情況是,保存在語(yǔ)言上有意義但區(qū)域性不明確的數(shù)據(jù)。
- 不要使用 String.Compare 或 CompareTo 方法的重載和用于確定兩個(gè)字符串是否相等的返回值為 0 的測(cè)試。
顯式指定字符串比較
重載 .NET 中大部分字符串操作方法。 通常,一個(gè)或多個(gè)重載會(huì)接受默認(rèn)設(shè)置,然而其他重載則不接受默認(rèn)設(shè)置,而是定義比較或操作字符串的精確方式。 大多數(shù)不依賴于默認(rèn)設(shè)置的方法都包括 StringComparison類型的參數(shù),該參數(shù)是按區(qū)域性和大小寫為字符串比較顯式指定規(guī)則的枚舉。 下表描述StringComparison 枚舉成員。
StringComparison 成員 | 描述 |
---|---|
CurrentCulture | 使用當(dāng)前區(qū)域性執(zhí)行區(qū)分大小寫的比較。 |
CurrentCultureIgnoreCase | 使用當(dāng)前區(qū)域性執(zhí)行不區(qū)分大小寫的比較。 |
InvariantCulture | 使用固定區(qū)域性執(zhí)行區(qū)分大小寫的比較。 |
InvariantCultureIgnoreCase | 使用固定區(qū)域性執(zhí)行不區(qū)分大小寫的比較。 |
Ordinal | 執(zhí)行序號(hào)比較。 |
OrdinalIgnoreCase | 執(zhí)行不區(qū)分大小寫的序號(hào)比較。 |
例如, IndexOf 方法(它返回 String 對(duì)象中與某字符或字符串匹配的子字符串的索引)具有九種重載:
- 默認(rèn)情況下,IndexOf(Char), IndexOf(Char, Int32)和 IndexOf(Char, Int32, Int32)對(duì)字符串中的字符執(zhí)行序號(hào)(區(qū)分大小寫但不區(qū)分區(qū)域性的)搜索。
- 默認(rèn)情況下,IndexOf(String), IndexOf(String, Int32)和 IndexOf(String, Int32, Int32)對(duì)字符串中的子字符串執(zhí)行區(qū)分大小寫且區(qū)分區(qū)域性的搜索。
- IndexOf(String, StringComparison)、 IndexOf(String, Int32, StringComparison)和 IndexOf(String, Int32, Int32, StringComparison),其中包括 StringComparison 類型的參數(shù),該類型允許指定比較形式。
我們建議選擇不使用默認(rèn)值的重載,原因如下:
- 具有默認(rèn)參數(shù)的一些重載(在字符串實(shí)例中搜索 Char 的重載)執(zhí)行序號(hào)比較,而其他重載(在字符串實(shí)例中搜索字符串的重載)執(zhí)行的是區(qū)分區(qū)域性的比較。 要記住哪種方法使用哪個(gè)默認(rèn)值并非易事,并很容易混淆重載。
- 依賴于方法調(diào)用默認(rèn)值的代碼的意圖并不清楚。 在下面依賴于默認(rèn)值的示例中,很難了解開發(fā)人員對(duì)兩個(gè)字符串的實(shí)際意圖是執(zhí)行序號(hào)比較還是語(yǔ)言比較,或者
protocol
和“http”之間存在的大小寫差異是否會(huì)導(dǎo)致相等性測(cè)試返回false
類型的參數(shù)的方法重載。
string protocol = GetProtocol(url); if (String.Equals(protocol, "http")) { // ...Code to handle HTTP protocol. } else { throw new InvalidOperationException(); }
一般情況下,我們建議調(diào)用不依賴于默認(rèn)設(shè)置的方法,因?yàn)檫@會(huì)明確代碼的意圖。 這進(jìn)而使代碼更具可讀性且更易于調(diào)試和維護(hù)。 下面的示例解決了前面示例中提出的問(wèn)題。 使用序號(hào)比較并且忽略大小寫差異。
string protocol = GetProtocol(url); if (String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) { // ...Code to handle HTTP protocol. } else { throw new InvalidOperationException(); }
字符串比較的詳細(xì)信息
字符串比較是許多字符串相關(guān)操作的核心,特別是排序和相等性測(cè)試操作。 字符串以確定的順序進(jìn)行排序:如果在排序的字符串列表中,“my”出現(xiàn)在“string”之前,則“my”必定小于或等于“string”。 此外,比較可隱式確定相等性。 對(duì)于認(rèn)為是相等的字符串,比較操作將返回零。 對(duì)此很好的解釋是兩個(gè)字符串都不小于對(duì)方。 涉及到字符串的最有意義的操作包括這些步驟中的一個(gè)或兩個(gè)步驟:與另一個(gè)字符串進(jìn)行比較和執(zhí)行明確的排序操作。
[!NOTE]
可以下載排序權(quán)重表,這是一組文本文件,其中包含有關(guān) Windows 操作系統(tǒng)排序和比較操作中所使用的字符權(quán)重的信息,也可以下載默認(rèn) Unicode 排序元素表,這是適用于 Linux 和 macOS 的最新版排序權(quán)重表。 Linux 和 macOS 上的特定排序權(quán)重表版本取決于系統(tǒng)上安裝的 International Components for Unicode 庫(kù)的版本。 有關(guān) ICU 版本及它們所實(shí)現(xiàn)的 Unicode 版本的信息,請(qǐng)參閱下載 ICU。
但是,評(píng)估兩個(gè)字符串的相等性或排序順序不會(huì)生成一個(gè)正確的結(jié)果;其結(jié)果取決于用于比較這兩個(gè)字符串的條件。 特別是,序號(hào)或基于當(dāng)前區(qū)域性或固定區(qū)域性(基于英語(yǔ)語(yǔ)言的區(qū)域設(shè)置不明確的區(qū)域性)的大小寫和排序約定的字符串比較可能會(huì)產(chǎn)生不同的結(jié)果。
此外,使用不同 .NET 版本或在不同操作系統(tǒng)或不同的操作系統(tǒng)版本上使用 .NET 進(jìn)行字符串比較時(shí),返回的結(jié)果可能不同。 有關(guān)詳細(xì)信息,請(qǐng)參閱字符串和 Unicode 標(biāo)準(zhǔn)。
使用當(dāng)前區(qū)域性的字符串比較
一個(gè)條件涉及在比較字符串時(shí)使用當(dāng)前區(qū)域性的約定。 基于當(dāng)前區(qū)域性的比較使用線程的當(dāng)前區(qū)域性或區(qū)域設(shè)置。 如果用戶未設(shè)置該區(qū)域性,則默認(rèn)為“控制面板”中“區(qū)域選項(xiàng)” 窗口中的設(shè)置。 當(dāng)數(shù)據(jù)與語(yǔ)言相關(guān)并反映區(qū)分區(qū)域性的用戶交互時(shí),應(yīng)始終使用基于當(dāng)前區(qū)域性的比較。
但是,當(dāng)區(qū)域性發(fā)生更改時(shí),.NET 中的比較和大小寫行為也發(fā)生更改。 如果執(zhí)行應(yīng)用程序的計(jì)算機(jī)與用于開發(fā)該應(yīng)用程序的計(jì)算機(jī)具有不同的區(qū)域性,或者執(zhí)行線程改變它的區(qū)域性,則會(huì)發(fā)生這種情況。 此行為是有意而為之的,但許多開發(fā)人員不易察覺此行為。 下面的示例說(shuō)明了美國(guó)英語(yǔ)(“en-US”)與瑞典語(yǔ)(“sv-SE”)區(qū)域性在排序順序中的差異。 請(qǐng)注意,單詞“ångström”、“Windows”和“Visual Studio”將出現(xiàn)在已排序的字符串?dāng)?shù)組的不同位置。
using System; using System.Globalization; using System.Threading; public class Example { public static void Main() { string[] values= { "able", "?ngstr?m", "apple", "?ble", "Windows", "Visual Studio" }; 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); } private static void DisplayArray(string[] values) { Console.WriteLine("Sorting using the {0} culture:", CultureInfo.CurrentCulture.Name); foreach (string value in values) Console.WriteLine(" {0}", 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 // ?ble // apple // Windows // Visual Studio // ?ngstr?m
使用當(dāng)前區(qū)域性的不區(qū)分大小寫比較和區(qū)分區(qū)域性的比較是相同的,只不過(guò)前者忽略由線程的當(dāng)前區(qū)域性指示的大小寫。 這種情況也可表明它的排序順序。
以下方法默認(rèn)利用使用當(dāng)前區(qū)域性語(yǔ)義的比較:
- 不包括String.Compare 參數(shù)的 StringComparison 重載。
- String.CompareTo 重載。
- 默認(rèn) String.StartsWith(String) 方法和具有 String.StartsWith(String, Boolean, CultureInfo) null nullCultureInfo 重載。
- 默認(rèn) String.EndsWith(String) 方法和需要使用 nullCultureInfo 參數(shù)的 String.EndsWith(String, Boolean, CultureInfo) 方法。
- 接受String.IndexOf 作為搜索參數(shù)且不包含 String 參數(shù)的 StringComparison 重載。
- 接受String.LastIndexOf 作為搜索參數(shù)且不包含 String 參數(shù)的 StringComparison 重載。
總之,我們建議調(diào)用具有 <xref:System.StringComparison> 參數(shù)的重載,以便明確方法調(diào)用的意圖。
當(dāng)從語(yǔ)言角度解釋非語(yǔ)言的字符串?dāng)?shù)據(jù),或利用其他區(qū)域性的約定解釋某個(gè)特定區(qū)域性中的字符串時(shí),則會(huì)發(fā)生或大或小的錯(cuò)誤。 土耳其語(yǔ) I 問(wèn)題便是一個(gè)規(guī)范示例。
對(duì)于幾乎所有拉丁字母來(lái)講(包括美國(guó)英語(yǔ)),字符“i”(\u0069) 是字符“I”(\u0049) 的小寫形式。 此大小寫規(guī)則快速成為在此類區(qū)域性中編程的人員的默認(rèn)設(shè)置。 但是,土耳其語(yǔ)(“tr-TR”)字母表中包含一個(gè)“帶有點(diǎn)的 I”的字符“?”(\u0130),該字符是“i”的大寫形式。 土耳其語(yǔ)還包括一個(gè)小寫“不帶點(diǎn)的 i”字符,即為“?”(\u0131),該字符的大寫形式為“I”。 阿塞拜疆語(yǔ)(“az”)區(qū)域也會(huì)出現(xiàn)這種情況。
因此,關(guān)于將“i”變?yōu)榇髮懟驅(qū)?ldquo;I”變?yōu)樾懙募僭O(shè)并非在所有區(qū)域性中都是有效的。 如果為字符串比較例程使用默認(rèn)重載,則它們可能會(huì)因區(qū)域性不同而異。 如果對(duì)非語(yǔ)言的數(shù)據(jù)進(jìn)行比較,使用默認(rèn)重載會(huì)產(chǎn)生不良后果,如以下對(duì)字符串“file”和“FILE”執(zhí)行不區(qū)分大小寫的比較嘗試所示。
using System; using System.Globalization; using System.Threading; public class Example { public static void Main() { string fileUrl = "file"; Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); Console.WriteLine("Culture = {0}", Thread.CurrentThread.CurrentCulture.DisplayName); Console.WriteLine("(file == FILE) = {0}", fileUrl.StartsWith("FILE", true, null)); Console.WriteLine(); Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR"); Console.WriteLine("Culture = {0}", Thread.CurrentThread.CurrentCulture.DisplayName); Console.WriteLine("(file == FILE) = {0}", fileUrl.StartsWith("FILE", true, null)); } } // The example displays the following output: // Culture = English (United States) // (file == FILE) = True // // Culture = Turkish (Turkey) // (file == FILE) = False
如果無(wú)意中在安全敏感設(shè)置中使用了區(qū)域性,則此比較會(huì)導(dǎo)致發(fā)生重大問(wèn)題,如以下示例所示。 如果當(dāng)前區(qū)域性為美國(guó)英語(yǔ),則 IsFileURI("file:")
等方法調(diào)用將返回 true
;但如果當(dāng)前區(qū)域性為土耳其語(yǔ),則將返回 false
。 因此,在土耳其語(yǔ)系統(tǒng)中,有人可能會(huì)避開阻止訪問(wèn)以“FILE:”開頭的不區(qū)分大小寫的安全措施。
public static bool IsFileURI(String path) { return path.StartsWith("FILE:", true, null); }
在這種情況下,由于“file:”會(huì)被解釋為非語(yǔ)言的、不區(qū)分區(qū)域性的標(biāo)識(shí)符,因此,應(yīng)按照下面的示例所示編寫代碼:
public static bool IsFileURI(string path) { return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase); }
序號(hào)字符串操作
在方法調(diào)用中指定 StringComparison.Ordinal 或 StringComparison.OrdinalIgnoreCase 值表示非語(yǔ)言比較,這種比較忽略了自然語(yǔ)言的特性。 利用 StringComparison 值調(diào)用的方法將字符串操作決策建立在簡(jiǎn)單的字節(jié)比較的基礎(chǔ)之上,而不是按區(qū)域性參數(shù)化的大小寫或相等表。 在大多數(shù)情況下,這種方法最符合字符串的預(yù)期解釋,并使代碼更快更可靠。
序號(hào)比較就是字符串比較,在這種比較中,將比較每個(gè)字符串中的每個(gè)字節(jié)且不進(jìn)行語(yǔ)言解釋;例如,“windows”不匹配“Windows”。 實(shí)質(zhì)上,這是對(duì) C 運(yùn)行時(shí) strcmp 函數(shù)的調(diào)用。 當(dāng)上下文指示應(yīng)完全匹配字符串或要求保守匹配策略時(shí),請(qǐng)使用這種比較。 此外,序號(hào)比較是最快的比較操作,因?yàn)樗诖_定結(jié)果時(shí)不應(yīng)用任何語(yǔ)言規(guī)則。
.NET 中的字符串可以包括嵌入的空字符。 序號(hào)比較與區(qū)分區(qū)域性的比較(包括使用固定區(qū)域性的比較)之間最明顯的區(qū)別之一是對(duì)字符串中嵌入的空字符的處理方式。 當(dāng)使用 String.Compare 和 String.Equals 方法執(zhí)行區(qū)分區(qū)域性的比較(包括使用固定區(qū)域性的比較)時(shí),將忽略這些字符。 因此,在區(qū)分區(qū)域性的比較中,包含嵌入的空字符的字符串可視為等于不包含空字符的字符串。
[!IMPORTANT]
盡管字符串比較方法忽略嵌入的空字符,但是 String.Contains、 String.EndsWith、 String.IndexOf、 String.LastIndexOf和 String.StartsWith 等字符串搜索方法并不會(huì)忽略這些字符。
下面的示例對(duì)字符串“Aa”與在“A”和“a”之間嵌入了多個(gè)空字符的相似字符串進(jìn)行區(qū)分區(qū)域性的比較,并顯示如何將這兩個(gè)字符串視為相等的字符串:
using System; public class Example { public static void Main() { string str1 = "Aa"; string str2 = "A" + new String('\u0000', 3) + "a"; Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", str1, ShowBytes(str1), str2, ShowBytes(str2)); Console.WriteLine(" With String.Compare:"); Console.WriteLine(" Current Culture: {0}", String.Compare(str1, str2, StringComparison.CurrentCulture)); Console.WriteLine(" Invariant Culture: {0}", String.Compare(str1, str2, StringComparison.InvariantCulture)); Console.WriteLine(" With String.Equals:"); Console.WriteLine(" Current Culture: {0}", String.Equals(str1, str2, StringComparison.CurrentCulture)); Console.WriteLine(" Invariant Culture: {0}", String.Equals(str1, str2, StringComparison.InvariantCulture)); } private static string ShowBytes(string str) { string hexString = String.Empty; for (int ctr = 0; ctr < str.Length; ctr++) { string result = String.Empty; 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: // Current Culture: 0 // Invariant Culture: 0 // With String.Equals: // Current Culture: True // Invariant Culture: True
但是,當(dāng)使用序號(hào)比較時(shí),這兩個(gè)字符串不會(huì)視為相等,如下面的示例所示:
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", str1, ShowBytes(str1), str2, ShowBytes(str2)); Console.WriteLine(" With String.Compare:"); Console.WriteLine(" Ordinal: {0}", String.Compare(str1, str2, StringComparison.Ordinal)); Console.WriteLine(" With String.Equals:"); Console.WriteLine(" Ordinal: {0}", String.Equals(str1, str2, StringComparison.Ordinal)); // 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
不區(qū)分大小寫的序號(hào)比較是第二種最保守的方法。 這些比較會(huì)忽略大多數(shù)的大小寫;例如,“windows”會(huì)匹配“Windows”。 在處理 ASCII 字符時(shí),此策略等同于 StringComparison.Ordinal,只不過(guò)它會(huì)忽略常用的 ASCII 大小寫。 因此,[A, Z] (\u0041-\u005A) 中的任何字符都會(huì)匹配 [a,z] (\u0061-\007A) 中的相應(yīng)字符。 超出 ASCII 范圍的大小寫使用固定區(qū)域性的表。 因此,下面的比較:
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);
等效于(但會(huì)更快)這種比較:
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal);
這些比較仍非???。
StringComparison.Ordinal 和 StringComparison.OrdinalIgnoreCase 均直接使用二進(jìn)制值并最適合匹配。 當(dāng)不確定比較設(shè)置時(shí),請(qǐng)使用這兩個(gè)值中的其中一個(gè)。 不過(guò),由于它們執(zhí)行逐字節(jié)比較,因此不會(huì)按照語(yǔ)言排序順序(如英語(yǔ)詞典)進(jìn)行排序,而是按照二進(jìn)制排序順序。 如果向用戶顯示結(jié)果,則在大多數(shù)上下文中結(jié)果都看上去不正常。
序號(hào)語(yǔ)義是不包括 String.Equals 參數(shù)(包括相等運(yùn)算符)的 StringComparison 重載的默認(rèn)項(xiàng)。 總之,我們建議調(diào)用具有 StringComparison 參數(shù)的重載。
使用固定區(qū)域性的字符串操作
具有固定區(qū)域性的比較使用由靜態(tài) CompareInfo 屬性返回的 CultureInfo.InvariantCulture 屬性。 此行為在所有系統(tǒng)中都相同;它會(huì)將其范圍外的任何字符轉(zhuǎn)換為其認(rèn)為等效的固定字符。 此策略對(duì)于在各個(gè)區(qū)域性中維護(hù)一組字符串行為很有用,但經(jīng)常產(chǎn)生意外的結(jié)果。
具有固定區(qū)域性的不區(qū)分大小寫的比較也使用由靜態(tài) CompareInfo 屬性返回的靜態(tài) CultureInfo.InvariantCulture 屬性以獲取比較信息。 所轉(zhuǎn)換字符中的任何大小寫差異都將被忽略。
使用 StringComparison.InvariantCulture 和 StringComparison.Ordinal 的比較對(duì) ASCII 字符串產(chǎn)生相同的作用。 但是, StringComparison.InvariantCulture 會(huì)做出可能不適用于解釋為一組字節(jié)的字符串的語(yǔ)言性決策。 還可以使用 CultureInfo.InvariantCulture.CompareInfo 對(duì)象使 Compare 方法將一組特定的字符解釋為等效字符。 例如,下面的等效字符在固定區(qū)域性中是有效的:
InvariantCulture: a + ? = å
如果 A 字符的小寫拉丁字母“a”(\u0061) 旁邊有上方組合圓圈字符“+ " ?”(\u030a),A 字符就會(huì)被解釋為,上方帶有圓圈的小寫拉丁字母“å”(\u00e5)。 如下面的示例所示,此行為不同于序號(hào)比較。
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
當(dāng)解釋其中出現(xiàn)如“å”組合的文件名稱、cookie 或其他內(nèi)容時(shí),序號(hào)比較仍會(huì)提供最透明和最合適的行為。
總的來(lái)說(shuō),固定區(qū)域性具有極少的對(duì)比較有用的屬性。 它會(huì)以與語(yǔ)言相關(guān)的方式執(zhí)行比較,使其無(wú)法保證完整的符號(hào)等效性,但它并不是任何區(qū)域性中顯示的選擇。 使用 StringComparison.InvariantCulture 進(jìn)行比較的其中一個(gè)原因是為多個(gè)區(qū)域性相同的顯示保留已排序的數(shù)據(jù)。 例如,如果應(yīng)用程序附帶包含用于顯示的已排序標(biāo)識(shí)符列表的大型數(shù)據(jù)文件,則添加到此列表將需要使用固定條件樣式排序插入。
為方法調(diào)用選擇 StringComparison 成員
下表概述了從語(yǔ)義字符串上下文到 StringComparison 枚舉成員的映射:
數(shù)據(jù) | 行為 | 相應(yīng) System.StringComparison value |
---|---|---|
區(qū)分大小寫的內(nèi)部標(biāo)識(shí)符。 區(qū)分大小寫的標(biāo)準(zhǔn)標(biāo)識(shí)符(例如 XML 和 HTTP)。 區(qū)分大小寫的安全相關(guān)設(shè)置。 | 字節(jié)完全匹配的非語(yǔ)言標(biāo)識(shí)符。 | Ordinal |
不區(qū)分大小寫的內(nèi)部標(biāo)識(shí)符。 不區(qū)分大小寫的標(biāo)準(zhǔn)標(biāo)識(shí)符(例如 XML 和 HTTP)。 文件路徑。 注冊(cè)表項(xiàng)和值。 環(huán)境變量。 資源標(biāo)識(shí)符(例如,句柄名稱)。 不區(qū)分大小寫的安全相關(guān)設(shè)置。 | 無(wú)關(guān)大小寫的非語(yǔ)言標(biāo)識(shí)符;尤其是存儲(chǔ)在大多數(shù) Windows 系統(tǒng)服務(wù)中的數(shù)據(jù)。 | OrdinalIgnoreCase |
某些保留的、與語(yǔ)言相關(guān)的數(shù)據(jù)。 需要固定排序順序的語(yǔ)言數(shù)據(jù)的顯示。 | 仍與語(yǔ)言相關(guān)的區(qū)域性不明確數(shù)據(jù)。 | InvariantCulture - 或 - InvariantCultureIgnoreCase |
向用戶顯示的數(shù)據(jù)。 大多數(shù)用戶輸入。 | 需要本地語(yǔ)言自定義的數(shù)據(jù)。 | CurrentCulture - 或 - CurrentCultureIgnoreCase |
.NET 中的常見字符串比較方法
以下各節(jié)介紹最常用于執(zhí)行字符串比較的方法。
String.Compare
默認(rèn)解釋: StringComparison.CurrentCulture。
作為字符串解釋最核心的操作,應(yīng)根據(jù)當(dāng)前區(qū)域性檢查這些方法調(diào)用的所有實(shí)例來(lái)確定是否應(yīng)該從區(qū)域性(符號(hào))解釋或分離字符串。 通常情況下,采用后者,并且應(yīng)改用 StringComparison.Ordinal 比較。
System.Globalization.CompareInfo 屬性返回的 CultureInfo.CompareInfo 類也包括利用 Compare 標(biāo)記枚舉的方式提供大量匹配選項(xiàng)(序號(hào)、忽略空白、忽略假名類型等)的 CompareOptions 方法。
String.CompareTo
默認(rèn)解釋: StringComparison.CurrentCulture。
此方法當(dāng)前不提供指定 StringComparison 類型的重載。 通常可以將此方法轉(zhuǎn)換為建議的 String.Compare(String, String, StringComparison) 形式。
實(shí)現(xiàn) IComparable 和 IComparable 接口的類型實(shí)現(xiàn)此方法。 由于它不提供 StringComparison 參數(shù)選項(xiàng),因此實(shí)現(xiàn)類型經(jīng)常使用戶在其構(gòu)造函數(shù)中指定 StringComparer。 下面的示例定義 FileName 類,其類構(gòu)造函數(shù)包括 StringComparer 參數(shù)。 然后此 StringComparer 對(duì)象將用于 FileName.CompareTo 方法。
using System; public class FileName : IComparable { string fname; StringComparer comparer; public FileName(string name, StringComparer comparer) { if (String.IsNullOrEmpty(name)) throw new ArgumentNullException("name"); this.fname = name; if (comparer != null) this.comparer = comparer; else this.comparer = StringComparer.OrdinalIgnoreCase; } public string Name { get { return fname; } } public int CompareTo(object obj) { if (obj == null) return 1; if (! (obj is FileName)) return comparer.Compare(this.fname, obj.ToString()); else return comparer.Compare(this.fname, ((FileName) obj).Name); } }
String.Equals
默認(rèn)解釋: StringComparison.Ordinal。
String 類可通過(guò)調(diào)用靜態(tài)或?qū)嵗?Equals 方法重載或使用靜態(tài)相等運(yùn)算符,測(cè)試是否相等。 默認(rèn)情況下,重載和運(yùn)算符使用序號(hào)比較。 但是,我們?nèi)匀唤ㄗh調(diào)用顯式指定 StringComparison 類型的重載,即使想要執(zhí)行序號(hào)比較;這將更輕松地搜索特定字符串解釋的代碼。
String.ToUpper 和 String.ToLower
默認(rèn)解釋: StringComparison.CurrentCulture。
應(yīng)謹(jǐn)慎使用這些方法,因?yàn)閷⒆址畯?qiáng)制為大寫或小寫經(jīng)常用作在不考慮大小寫的情況下比較字符串的較小規(guī)范化。 如果是這樣,請(qǐng)考慮使用不區(qū)分大小寫的比較。
還可以使用 String.ToUpperInvariant 和 String.ToLowerInvariant 方法。 ToUpperInvariant 是規(guī)范化大小寫的標(biāo)準(zhǔn)方式。 使用 StringComparison.OrdinalIgnoreCase 進(jìn)行的比較在行為上是兩個(gè)調(diào)用的組合:對(duì)兩個(gè)字符串參數(shù)調(diào)用 ToUpperInvariant ,并使用 StringComparison.Ordinal執(zhí)行比較。
通過(guò)向方法傳遞表示區(qū)域性的 CultureInfo 對(duì)象,重載也已可用于轉(zhuǎn)換該特性區(qū)域性中的大寫和小寫字母。
Char.ToUpper 和 Char.ToLower
默認(rèn)解釋: StringComparison.CurrentCulture。
這些方法的工作原理類似于上一節(jié)中所述的 String.ToUpper 和 String.ToLower 方法。
String.StartsWith 和 String.EndsWith
默認(rèn)解釋: StringComparison.CurrentCulture。
默認(rèn)情況下,這兩種方法執(zhí)行區(qū)分區(qū)域性的比較。
String.IndexOf 和 String.LastIndexOf
默認(rèn)解釋: StringComparison.CurrentCulture。
這些方法的默認(rèn)重載如何執(zhí)行比較方面缺乏一致性。 包含 String.IndexOf 參數(shù)的所有 String.LastIndexOf 和 Char 方法都執(zhí)行序號(hào)比較,但是包含 String.IndexOf 參數(shù)的默認(rèn) String.LastIndexOf 和 String 方法都執(zhí)行區(qū)分區(qū)域性的比較。
如果調(diào)用 String.IndexOf(String) 或 String.LastIndexOf(String) 方法并向其傳遞一個(gè)字符串以在當(dāng)前實(shí)例中查找,那么我們建議調(diào)用顯式指定 StringComparison 類型的重載。 包括 Char 參數(shù)的重載不允許指定 StringComparison 類型。
間接執(zhí)行字符串比較的方法
將字符串比較作為核心操作的一些非字符串方法使用 StringComparer 類型。 StringComparer 類型包含六個(gè)返回 StringComparer 實(shí)例的靜態(tài)屬性,這些實(shí)例的 StringComparer.Compare 方法可執(zhí)行以下類型的字符串比較:
- 使用當(dāng)前區(qū)域性的區(qū)分區(qū)域性的字符串比較。 此 StringComparer 對(duì)象由 StringComparer.CurrentCulture 屬性返回。
- 使用當(dāng)前區(qū)域性的不區(qū)分區(qū)域性的比較。 此 StringComparer 對(duì)象由 StringComparer.CurrentCultureIgnoreCase 屬性返回。
- 使用固定區(qū)域性的單詞比較規(guī)則的不區(qū)分區(qū)域性的比較。 此 StringComparer 對(duì)象由 StringComparer.InvariantCulture 屬性返回。
- 使用固定區(qū)域性的單詞比較規(guī)則的不區(qū)分大小寫和不區(qū)分區(qū)域性的比較。 此 StringComparer 對(duì)象由 StringComparer.InvariantCultureIgnoreCase 屬性返回。
- 序號(hào)比較。 此 StringComparer 對(duì)象由 StringComparer.Ordinal 屬性返回。
- 不區(qū)分大小寫的序號(hào)比較。 此 StringComparer 對(duì)象由 StringComparer.OrdinalIgnoreCase 屬性返回。
Array.Sort 和 Array.BinarySearch
默認(rèn)解釋: StringComparison.CurrentCulture。
當(dāng)在集合中存儲(chǔ)任何數(shù)據(jù),或?qū)⒊志脭?shù)據(jù)從文件或數(shù)據(jù)庫(kù)中讀取到集合中時(shí),切換當(dāng)前區(qū)域性可能會(huì)使集合中的固定條件無(wú)效。 Array.BinarySearch 方法假定已對(duì)數(shù)組中要搜索的元素排序。 若要對(duì)數(shù)組中的任何字符串元素進(jìn)行排序, Array.Sort 方法會(huì)調(diào)用 String.Compare 方法以對(duì)各個(gè)元素進(jìn)行排序。 如果對(duì)數(shù)組進(jìn)行排序和搜索其內(nèi)容的時(shí)間范圍內(nèi)區(qū)域性發(fā)生變化,那么使用區(qū)分區(qū)域性的比較器會(huì)很危險(xiǎn)。 例如在下面的代碼中,是在由 Thread.CurrentThread.CurrentCulture 屬性。 如果在調(diào)用 StoreNames 和 DoesNameExist之間更改了區(qū)域性(尤其是數(shù)組內(nèi)容保存在兩個(gè)方法調(diào)用之間的某個(gè)位置),那么二進(jìn)制搜索可能會(huì)失敗。
// Incorrect. string []storedNames; public void StoreNames(string [] names) { int index = 0; storedNames = new string[names.Length]; foreach (string name in names) { this.storedNames[index++] = name; } Array.Sort(names); // Line A. } public bool DoesNameExist(string name) { return (Array.BinarySearch(this.storedNames, name) >= 0); // Line B. }
建議的變體將顯示在下面使用相同序號(hào)(不區(qū)分區(qū)域性)比較方法進(jìn)行排序并搜索數(shù)組的示例中。 在這兩個(gè)示例中,更改代碼會(huì)反映在標(biāo)記 Line A
和 Line B
的代碼行中。
// Correct. string []storedNames; public void StoreNames(string [] names) { int index = 0; storedNames = new string[names.Length]; foreach (string name in names) { this.storedNames[index++] = name; } Array.Sort(names, StringComparer.Ordinal); // Line A. } public bool DoesNameExist(string name) { return (Array.BinarySearch(this.storedNames, name, StringComparer.Ordinal) >= 0); // Line B. }
如果此數(shù)據(jù)永久保留并跨區(qū)域性移動(dòng),并且使用排序來(lái)向用戶顯示此數(shù)據(jù),則可以考慮使用 StringComparison.InvariantCulture,其語(yǔ)言操作可獲得更好的用戶輸出且不受區(qū)域性更改的影響。 下面的示例修改了前面兩個(gè)示例,使用固定區(qū)域性對(duì)數(shù)組進(jìn)行排序和搜索。
// Correct. string []storedNames; public void StoreNames(string [] names) { int index = 0; storedNames = new string[names.Length]; foreach (string name in names) { this.storedNames[index++] = name; } Array.Sort(names, StringComparer.InvariantCulture); // Line A. } public bool DoesNameExist(string name) { return (Array.BinarySearch(this.storedNames, name, StringComparer.InvariantCulture) >= 0); // Line B. }
集合示例:哈希表構(gòu)造函數(shù)
哈希字符串提供了第二個(gè)運(yùn)算示例,該運(yùn)算受比較字符串的方式影響。
下面的示例實(shí)例化 Hashtable 對(duì)象,方法是向其傳遞由 StringComparer 屬性返回的 StringComparer.OrdinalIgnoreCase 對(duì)象。 由于派生自 StringComparer 的類 StringComparer 實(shí)現(xiàn) IEqualityComparer 接口,其 GetHashCode 方法用于計(jì)算哈希表中的字符串的哈希代碼。
const int initialTableCapacity = 100; Hashtable h; public void PopulateFileTable(string directory) { h = new Hashtable(initialTableCapacity, StringComparer.OrdinalIgnoreCase); foreach (string file in Directory.GetFiles(directory)) h.Add(file, File.GetCreationTime(file)); } public void PrintCreationTime(string targetFile) { Object dt = h[targetFile]; if (dt != null) { Console.WriteLine("File {0} was created at time {1}.", targetFile, (DateTime) dt); } else { Console.WriteLine("File {0} does not exist.", targetFile); } }
請(qǐng)參閱
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
.NET Core 3.0之創(chuàng)建基于Consul的Configuration擴(kuò)展組件
在本文里小編給大家分享了關(guān)于.NET Core 3.0之創(chuàng)建基于Consul的Configuration擴(kuò)展組件相關(guān)知識(shí)點(diǎn),需要的朋友們學(xué)習(xí)下。2019-05-05FreeTextBox(版本3.1.6)在ASP.Net 2.0中使用方法
在ASP.Net 2.0中使用,只需要2個(gè)文件:FreeTextBox.DLL和ftb.imagegallery.aspx2009-11-11淺談ASP.NET Core 2.0 布局頁(yè)面(譯)
本篇文章主要介紹了淺談ASP.NET Core 2.0 布局頁(yè)面(譯),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11C#(.NET)數(shù)據(jù)訪問(wèn)連接、查詢、插入等操作的封裝類
一個(gè)C#(.NET)數(shù)據(jù)訪問(wèn)連接、查詢、插入等操作的封裝類2008-05-05ASP.NET MVC5+EF6+EasyUI后臺(tái)管理系統(tǒng) 微信公眾平臺(tái)開發(fā)之資源環(huán)境準(zhǔn)備
這篇文章主要介紹了ASP.NET MVC5+EF6+EasyUI后臺(tái)管理系統(tǒng),微信公眾平臺(tái)開發(fā)之資源環(huán)境準(zhǔn)備,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09Asp.net開發(fā)常用的51個(gè)非常實(shí)用的代碼
Asp.net開發(fā)常用的51個(gè)非常實(shí)用的代碼,需要的朋友可以參考下。2010-06-06"PageMethods未定義"或"對(duì)象不支持此屬性或方法"解決方法分享
PageMethods未定義或?qū)ο蟛恢С执藢傩曰蚍椒ń鉀Q方法,需要的朋友可以參考下。2010-12-12