.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ū)域性無關(guān)的字符串?dāng)?shù)據(jù)解釋為區(qū)分區(qū)域性,則應(yīng)用程序代碼會(huì)遭遇細(xì)微的錯(cuò)誤、不佳的性能,在某些情況下,還會(huì)遭遇安全性問題。
本文介紹 .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)行與語言(例如,符號(hào))無關(guān)的比較時(shí),使用非語言的 StringComparison.Ordinal 或 StringComparison.OrdinalIgnoreCase 值,而不使用基于 CultureInfo.InvariantCulture 的字符串操作。
- 在規(guī)范化要比較的字符串時(shí),使用 String.ToUpperInvariant 方法而非 String.ToLowerInvariant 方法。
- 使用 String.Equals 方法的重載來測(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ù)例外情況是,保存在語言上有意義但區(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)比較還是語言比較,或者
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ù)。 下面的示例解決了前面示例中提出的問題。 使用序號(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ū)域性(基于英語語言的區(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ù)與語言相關(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ā)人員不易察覺此行為。 下面的示例說明了美國(guó)英語(“en-US”)與瑞典語(“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ū)域性的比較是相同的,只不過前者忽略由線程的當(dāng)前區(qū)域性指示的大小寫。 這種情況也可表明它的排序順序。
以下方法默認(rèn)利用使用當(dāng)前區(qū)域性語義的比較:
- 不包括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)從語言角度解釋非語言的字符串?dāng)?shù)據(jù),或利用其他區(qū)域性的約定解釋某個(gè)特定區(qū)域性中的字符串時(shí),則會(huì)發(fā)生或大或小的錯(cuò)誤。 土耳其語 I 問題便是一個(gè)規(guī)范示例。
對(duì)于幾乎所有拉丁字母來講(包括美國(guó)英語),字符“i”(\u0069) 是字符“I”(\u0049) 的小寫形式。 此大小寫規(guī)則快速成為在此類區(qū)域性中編程的人員的默認(rèn)設(shè)置。 但是,土耳其語(“tr-TR”)字母表中包含一個(gè)“帶有點(diǎn)的 I”的字符“?”(\u0130),該字符是“i”的大寫形式。 土耳其語還包括一個(gè)小寫“不帶點(diǎn)的 i”字符,即為“?”(\u0131),該字符的大寫形式為“I”。 阿塞拜疆語(“az”)區(qū)域也會(huì)出現(xiàn)這種情況。
因此,關(guān)于將“i”變?yōu)榇髮懟驅(qū)?ldquo;I”變?yōu)樾懙募僭O(shè)并非在所有區(qū)域性中都是有效的。 如果為字符串比較例程使用默認(rèn)重載,則它們可能會(huì)因區(qū)域性不同而異。 如果對(duì)非語言的數(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如果無意中在安全敏感設(shè)置中使用了區(qū)域性,則此比較會(huì)導(dǎo)致發(fā)生重大問題,如以下示例所示。 如果當(dāng)前區(qū)域性為美國(guó)英語,則 IsFileURI("file:") 等方法調(diào)用將返回 true;但如果當(dāng)前區(qū)域性為土耳其語,則將返回 false。 因此,在土耳其語系統(tǒng)中,有人可能會(huì)避開阻止訪問以“FILE:”開頭的不區(qū)分大小寫的安全措施。
public static bool IsFileURI(String path)
{
return path.StartsWith("FILE:", true, null);
}在這種情況下,由于“file:”會(huì)被解釋為非語言的、不區(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 值表示非語言比較,這種比較忽略了自然語言的特性。 利用 StringComparison 值調(diào)用的方法將字符串操作決策建立在簡(jiǎn)單的字節(jié)比較的基礎(chǔ)之上,而不是按區(qū)域性參數(shù)化的大小寫或相等表。 在大多數(shù)情況下,這種方法最符合字符串的預(yù)期解釋,并使代碼更快更可靠。
序號(hào)比較就是字符串比較,在這種比較中,將比較每個(gè)字符串中的每個(gè)字節(jié)且不進(jìn)行語言解釋;例如,“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)用任何語言規(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,只不過它會(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è)。 不過,由于它們執(zhí)行逐字節(jié)比較,因此不會(huì)按照語言排序順序(如英語詞典)進(jìn)行排序,而是按照二進(jìn)制排序順序。 如果向用戶顯示結(jié)果,則在大多數(shù)上下文中結(jié)果都看上去不正常。
序號(hào)語義是不包括 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é)的字符串的語言性決策。 還可以使用 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ì)提供最透明和最合適的行為。
總的來說,固定區(qū)域性具有極少的對(duì)比較有用的屬性。 它會(huì)以與語言相關(guān)的方式執(zhí)行比較,使其無法保證完整的符號(hào)等效性,但它并不是任何區(qū)域性中顯示的選擇。 使用 StringComparison.InvariantCulture 進(jìn)行比較的其中一個(gè)原因是為多個(gè)區(qū)域性相同的顯示保留已排序的數(shù)據(jù)。 例如,如果應(yīng)用程序附帶包含用于顯示的已排序標(biāo)識(shí)符列表的大型數(shù)據(jù)文件,則添加到此列表將需要使用固定條件樣式排序插入。
為方法調(diào)用選擇 StringComparison 成員
下表概述了從語義字符串上下文到 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é)完全匹配的非語言標(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è)置。 | 無關(guān)大小寫的非語言標(biāo)識(shí)符;尤其是存儲(chǔ)在大多數(shù) Windows 系統(tǒng)服務(wù)中的數(shù)據(jù)。 | OrdinalIgnoreCase |
| 某些保留的、與語言相關(guān)的數(shù)據(jù)。 需要固定排序順序的語言數(shù)據(jù)的顯示。 | 仍與語言相關(guān)的區(qū)域性不明確數(shù)據(jù)。 | InvariantCulture - 或 - InvariantCultureIgnoreCase |
| 向用戶顯示的數(shù)據(jù)。 大多數(shù)用戶輸入。 | 需要本地語言自定義的數(shù)據(jù)。 | CurrentCulture - 或 - CurrentCultureIgnoreCase |
.NET 中的常見字符串比較方法
以下各節(jié)介紹最常用于執(zhí)行字符串比較的方法。
String.Compare
默認(rèn)解釋: StringComparison.CurrentCulture。
作為字符串解釋最核心的操作,應(yīng)根據(jù)當(dāng)前區(qū)域性檢查這些方法調(diào)用的所有實(shí)例來確定是否應(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 類型的重載。 通??梢詫⒋朔椒ㄞD(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 類可通過調(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í)行比較。
通過向方法傳遞表示區(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ì)使集合中的固定條件無效。 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),并且使用排序來向用戶顯示此數(shù)據(jù),則可以考慮使用 StringComparison.InvariantCulture,其語言操作可獲得更好的用戶輸出且不受區(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)文章
某個(gè)aspx頁(yè)面突然死了連日志也沒有的解決方法
某個(gè)aspx頁(yè)面突然死了連日志也沒有,朋友提醒event viewer里看看,果然錯(cuò)誤在那里,有此情況的朋友可以參考下2013-08-08
asp.net core中如何使用cookie身份驗(yàn)證
這篇文章主要介紹了asp.net core中如何使用cookie身份驗(yàn)證的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
ASP.NET用DataSet導(dǎo)出到Excel的方法
ASP.NET用DataSet導(dǎo)出到Excel的方法,需要的朋友可以參考一下2013-03-03
ASP.NET?MVC實(shí)現(xiàn)登錄后跳轉(zhuǎn)到原界面
這篇文章介紹了ASP.NET?MVC實(shí)現(xiàn)登錄后跳轉(zhuǎn)到原界面的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09
ASP.NET頁(yè)面某些選項(xiàng)進(jìn)行提示判斷具體實(shí)現(xiàn)
針對(duì)asp.net頁(yè)面某些選項(xiàng)進(jìn)行提示判斷,比如當(dāng)保存一個(gè)信息時(shí)候,需要對(duì)當(dāng)前信息是否為空進(jìn)行判斷2013-06-06
詳解在ASP.NET Core中使用Angular2以及與Angular2的Token base身份認(rèn)證
這篇文章主要介紹了詳解在ASP.NET Core中使用Angular2以及與Angular2的Token base身份認(rèn)證,有興趣的可以了解一下。2016-12-12
ASP.net中保持頁(yè)面中滾動(dòng)條狀態(tài)的代碼
在ASP.Net頁(yè)面中,每次回發(fā)都會(huì)造成頁(yè)面回到頂部,那么怎樣讓它保持刷新頁(yè)面前的位置上,使得頁(yè)面提交后還在原來的位位置。2011-06-06

