在 .NET Framework 中使用字串的最佳作法

.NET Framework 對於開發當地語系化和全球化應用程式提供豐富的支援,且在執行一般作業時,例如排序和顯示字串,可以更輕鬆地套用目前文化特性或特定文件特性的慣例。 但是,排序或比較字串不一定都是區分文化特性的作業。 例如,在所有文化特性中,通常應該以相同的方式處理應用程式在內部使用的字串。 在以分區文化特性的方式來解譯不區分文化特性的字串資料時,例如 XML 標記、HTML 標記、使用者名稱、檔案路徑和系統物件的名稱,應用程式程式碼很容易發生潛在的 Bug、效能低落,在某些情況下甚至造成安全性問題。

本主題討論 .NET Framework 4 版 (含) 以後版本中的字串排序、比較和大小寫轉換方法、提出如何選取適當之字串處理方法的建議,並提供有關字串處理方法的其他資訊。

此主題包括下列章節:

  • 字串用法的建議

  • 明確指定字串比較

  • 字串比較的詳細資料

  • 選擇方法呼叫的 StringComparison 成員

  • .NET Framework 的一般字串比較方法

  • 間接執行字串比較的方法

字串用法的建議

當您以 .NET Framework 進行開發時,請在使用字串時遵循這些簡單的建議:

在使用字串時,請避免下列作法:

  • 不要使用未明確指定或隱含指定字串作業之字串比較規則的多載。

  • 在大部分情況下不要使用以 StringComparison.InvariantCulture 為基礎的字串作業。 少數情況例外,其中之一是您保存的資料具有語言意義,但不區分文化特性。

  • 不要以測試 String.CompareCompareTo 方法多載的傳回值是否為零的方式來判斷兩個字串是否相等。

回到頁首

明確指定字串比較

.NET Framework 中的字串操作方法大部分都是多載。 通常有一個或多個多載接受預設字串,其他多載不接受預設值,而是明確定義字串的比較或操作方式。 大部分不依賴預設值的方法都包含 StringComparison 型別的參數,該參數以列舉方式明確指定依文化特性和大小寫進行字串比較的規則。 下表描述 StringComparison 列舉成員。

StringComparison 成員

說明

CurrentCulture

使用目前文化特性執行區分大小寫的比較。

CurrentCultureIgnoreCase

使用目前文化特性執行不區分大小寫的比較。

InvariantCulture

使用不因國別而異的文化特性執行區分大小寫的比較。

InvariantCultureIgnoreCase

使用不因國別而異的文化特性執行不區分大小寫的比較。

Ordinal

執行序數比較。

OrdinalIgnoreCase

執行不區分大小寫的序數比較。

例如,傳回 String 物件中符合字元或字串之子字串索引的 IndexOf 方法,具有九個多載:

我們建議您選取不使用預設值的多載,原因如下:

  • 某些具有預設參數的多載 (在字串執行個體中搜尋 Char 的多載) 會執行序數比較,而其他多載 (在字串執行個體中搜尋字串的多載) 會區分文化特性。 很難記住哪些方法使用哪些預設值,也很容易分不清多載。

  • 依賴預設值來執行方法呼叫的程式碼意圖並不清楚。 在下列範例中 (依賴預設值),很難知道開發人員實際上是想要對兩個字串進行序數比較還是語言比較,或 protocol 與 "http" 之間的大小寫差異是否會造成相等測試傳回 false。

    Dim protocol As String = GetProtocol(url)       
    If String.Equals(protocol, "http") Then
       ' ...Code to handle HTTP protocol.
    Else
       Throw New InvalidOperationException()
    End If   
    
    string protocol = GetProtocol(url);       
    if (String.Equals(protocol, "http")) {
       // ...Code to handle HTTP protocol.
    }
    else {
       throw new InvalidOperationException();
    }
    

一般而言,我們建議您呼叫不依賴預設值的方法,因為這樣使得程式碼用途很明確。 此外,這還可以更易於理解程式碼,也更容易偵錯和維護。 下列範例與前面的程式碼相同,差別在於很明顯看出使用的是序數比較,且忽略大小寫的差異。

Dim protocol As String = GetProtocol(url)       
If String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase) Then
   ' ...Code to handle HTTP protocol.
Else
   Throw New InvalidOperationException()
End If   
string protocol = GetProtocol(url);       
if (String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) {
   // ...Code to handle HTTP protocol.
}
else {
   throw new InvalidOperationException();
}

回到頁首

字串比較的詳細資料

字串比較是許多字串相關作業的重點所在,尤其是在排序和測試是否相等時。 字串依固定順序排序:在已排序的字串清單中,如果 "my" 在 "string" 之前出現,則 "my" 比較起來一定小於或等於 "string"。 此外,比較也隱含定義相等性。 比較作業會對判斷為相等的字串傳回零。 明言之就是沒有任一字串比另一個字串更小。 牽涉到字串的實務作業大多包括下列其中一個或兩個程序:與另一個字串比較,以及執行妥善定義的排序作業。

然而,評估兩個字串是否相等或排序次序並不會產生單一、正確的結果,結果視用於比較字串的準則而定。 尤其,如果字串比較是序數形式,或以目前文化特性或不因國別而異之文化特性的大小寫和排序慣例為基礎 (以英語為主之不區分地區設定的文化特性),則可能產生不同的結果。

使用目前文化特性的字串比較

在比較字串時,有一種準則會用到目前文化特性的慣例。 以目前文化特性為基礎的比較會使用執行緒的目前文化特性或地區設定。 如果使用者未設定文化特性,則預設為 [控制台] 的 [地區選項] 視窗中的設定。 當資料與語言有關,或當資料可反映出區分文化特性的使用者互動時,您應該一律使用以目前文化特性為基礎的比較。

不過,.NET Framework 中的比較和大小寫轉換行為會隨著文化特性變更而改變。 當執行應用程式的電腦與開發該應用程式的電腦各有不同的文化特性時,或當執行中執行緒變更其文化特性時,就會發生這種情形。 此種行為有其用意,但對許多開發人員來說並不容易注意到。 下列範例說明與美國之間的排序次序差異。 美國 ("en-US") 與瑞典 ("sv-SE") 文化特性。 請注意,單字 "ångström"、"Windows" 和 "Visual Studio" 在已排序的字串陣列中出現在不同的位置。

Imports System.Globalization
Imports System.Threading

Module Example
   Public Sub Main()
      Dim values() As String = { "able", "ångström", "apple", _
                                 "Æble", "Windows", "Visual Studio" }
      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

    Private Sub DisplayArray(values() As String)
      Console.WRiteLine("Sorting using the {0} culture:", _ 
                        CultureInfo.CurrentCulture.Name)
      For Each value As String In values
         Console.WriteLine("   {0}", 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
'          Æble
'          apple
'          Windows
'          Visual Studio
'          ångström
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

使用目前文化特性的不區分大小寫比較,與區分文化特性的比較相同,差別在於會依照執行緒的目前文化特性而忽略大小寫。 這種行為的本身也可能反映在排序次序上。

下列方法預設會依照目前文化特性之語意來執行比較:

在任何情況下,我們建議您呼叫具有 StringComparison 參數的多載,以明確表達方法的意圖。

在以語言學來解譯非語言的字串資料時,或當來自特定文化特性的字串資料經由另一種文化特性的慣例來解譯時,可能浮現輕微和不太輕微的 Bug。 最明顯的例子就是 Turkish-I 問題。

在幾乎所有拉丁字母中,包括美國 英文,字元 "i" (\u0069) 是字元 "I" (\u0049) 的小寫。 這種大小寫規則很快成為以這種文化特性進行程式設計的人所採用的預設規則。 然而,土耳其 ("tr-TR") 字母包含「加上一點的 I」字元 "İ" (\u0130),這個字元是 "i" 的大寫。 土耳其文也包含 "不含點的 i" 小寫字元,即 "ı" (\u0131),這個字元轉換成大寫的 "I"。 阿澤里文 ("az") 文化特性中也會發生這個行為。

因此,對於所有文化特性而言,關於將 "i" 轉換成大寫或將 "I" 轉換成小寫的假設並不適用。 如果您在字串比較常式中使用預設多載,則預設多載會隨著文化特性的差異而不同。 如果要比較的資料不是語言,則使用預設多載會產生不適當的結果,如下對字串 "file" 和 "FILE" 嘗試執行不區分大小寫的比較所示。

Imports System.Globalization
Imports System.Threading

Module Example
   Public Sub Main()
      Dim 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, Nothing))
      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, Nothing))
   End Sub
End Module
' The example displays the following output:
'       Culture = English (United States)
'       (file == FILE) = True
'       
'       Culture = Turkish (Turkey)
'       (file == FILE) = False
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

如果在安全性敏感的設定中不慎使用文化特性,則這種比較會造成嚴重問題,如下列範例所示。 如果目前文化特性是美國英文,則方法呼叫 (例如 IsFileURI("file:")) 會傳回 true, 但如果目前文化特性是土耳其文,則會傳回 false。 因此,在土耳其文系統上,即使禁止存取以 "FILE:" 為開頭的不區分大小寫 URI,某人也可以規避這項安全性措施。

Public Shared Function IsFileURI(path As String) As Boolean 
   Return path.StartsWith("FILE:", True, Nothing)
End Function
public static bool IsFileURI(String path) 
{
   return path.StartsWith("FILE:", true, null);
}

在此情況下,因為 "file:" 只能解譯為非語言、不區分文化特性的識別項目,所以應該改以如下列範例所示來撰寫程式碼。

Public Shared Function IsFileURI(path As String) As Boolean 
    Return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase)
End Function
public static bool IsFileURI(string path) 
{
   path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
   return true;
}

不過,前一個範例使用 String.StartsWith(String, StringComparison) 方法來測試是否相等。 因為比較的目的是測試是否相等,而非排序字串,所以較佳替代作法是呼叫 Equals 方法,如下列範例所示。

Public Shared Function IsFileURI(path As String) As Boolean
   If (path.Length < 5) Then Return False

   Return String.Equals(path.Substring(0, 5), "FILE:", _ 
                        StringComparison.OrdinalIgnoreCase)
End Function   
public static bool IsFileURI(string path)
{
   if (path.Length < 5) return false;

   return String.Equals(path.Substring(0, 5), "FILE:", 
                        StringComparison.OrdinalIgnoreCase);
}   

序數字串作業

在方法呼叫中指定 StringComparison.OrdinalStringComparison.OrdinalIgnoreCase 值意謂著非語言比較,其中忽略自然語言的特性。 以這些 StringComparison 值叫用的方法會根據簡單的位元組比較來決定字串作業,而非根據以文化特性所參數化的大小寫或等值表格。 在大多數情況下,這種作法最符合預期的字串解譯,還可讓程式碼變得更快、更可靠。

序數比較是指在比較每個字串的每個位元組時不使用語言解譯的字串比較,例如 "windows" 不符合 "Windows"。 這在實質上會呼叫 C 執行階段 strcmp 函式。 當內容規定字串必須完全相符或需要保守的比對原則時,請使用這種比較。 此外,序數比較也是最快的比較作業,因為它在決定結果時沒有套用任何語言規則。

.NET Framework 中的字串可以包含內嵌的 null 字元。 在序數與區分文化特性的比較 (包括使用不因文化特性而異的比較) 之間,其中一項最明顯的差異是關於處理字串中內嵌的 null 字元。 當您使用 String.CompareString.Equals 方法來執行區分文化特性的比較時 (包括使用不因文化特性而異的比較),會忽略這些字元。 因此,在區分文化特性的比較中,包含與不包含內嵌 null 字元的字串可以視為相等。

重要事項重要事項

雖然字串比較方法會忽略內嵌的 null 字元,但字串搜尋方法不會忽略這種字元,例如 String.ContainsString.EndsWithString.IndexOfString.LastIndexOfString.StartsWith

下列範例對字串 "Aa" 與類似字串執行區分文化特性的比較 (這個類似字串在 "A" 和 "a" 之間包含數個內嵌的 null 字元),並且顯示這兩個字串如何被視為相等。

Module Example
   Public Sub Main()
      Dim str1 As String = "Aa"
      Dim str2 As String = "A" + New String(Convert.ToChar(0), 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))
   End Sub

   Private 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 = String.Empty
         result = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
         result = " " + result.Substring(0,2) + " " + result.Substring(2, 2)
         hexString += result
      Next
      Return hexString.Trim()
   End Function
End Module
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

不過,當您使用序數比較時,這兩個字串就不視為相等,如下列範例所示。

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

不區分大小寫的序數比較是第二個最保守的作法。 這些比較忽略大部分的大小寫,例如 "windows" 符合 "Windows"。 在處理 ASCII 字元時,這項原則相當於 StringComparison.Ordinal,差別在於會忽略常見的 ASCII 大小寫。 因此,[A, Z] (\u0041-\u005A) 中的任何字元都符合 [a,z] (\u0061-\007A) 中對應的字元。 ASCII 範圍外的大小寫則使用不因文化特性而異的表格對照。 因此,下列比較:

String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);

相當於這種比較 (而且更快)。

String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), 
               StringComparison.Ordinal)
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), 
               StringComparison.Ordinal);

這些比較還是非常快。

注意事項注意事項

StringComparison.OrdinalIgnoreCase 最能表示檔案系統、登錄機碼和值及環境變數的字串行為。

StringComparison.OrdinalStringComparison.OrdinalIgnoreCase 兩者都直接使用二進位值,最適合用來比對。 當您不確定比較設定時,請使用這兩個值的其中一個。 然而,因為它們是以位元組為單位逐一比較,所以不會依照語言排序次序來排序 (就像英文字典一樣),而是採用二進位排序次序。 在大多數情況下,使用者看到的結果可能很奇怪。

不包含 StringComparison 引數 (包括等號運算子) 的 String.Equals 多載以序數語意為預設。 在任何情況下,我們建議您呼叫具有 StringComparison 參數的多載。

使用不因文化特性而異的字串作業

採用不因文化特性而異的比較會使用靜態 CultureInfo.InvariantCulture 屬性所傳回的 CompareInfo 屬性。 這種行為在所有系統上都相同,它會將其範圍之外的任何字元轉譯成它認為是相等非變異字元的字元。 這項原則很適合跨文化特性來維護一套字串行為,但通常會產生非預期的結果。

採用不因文化特性而異的不分區大小寫比較,也會使用靜態 CultureInfo.InvariantCulture 屬性所傳回的靜態 CompareInfo 屬性來取得比較資訊。 這些轉譯的字元之間的任何大小寫差異都會忽略。

使用 StringComparison.InvariantCultureStringComparison.Ordinal 的比較在 ASCII 字串上的運作方式完全相同。 然而,對於必須解譯成一組位元組的字串,StringComparison.InvariantCulture 所做的語言決策就可能不適合。 CultureInfo.InvariantCulture.CompareInfo 物件可以使 Compare 方法將多組字元解譯成相等。 例如,下列等式在不因國別而異的文化特性之下有效:

InvariantCulture: a + ̊ = å

「拉丁小寫字母 A」字元 "a" (\u0061) 在緊鄰著「結合上圓圈」字元 "+ " ̊" (\u030a) 時,解譯成「拉丁小寫字母 A 帶上圓圈」字元 "å" (\u00e5)。 如下列範例如示,這種行為不同於序數比較。

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

在解譯檔名、Cookie,或可能出現像 "å" 一樣的組合的其他任何字串時,序數比較仍然會展現最明確和最適當的行為。

總之,不因國別而異的文化特性只有非常少的屬性,很適合用於比較。 它會以語言相關的方式進行比較,這樣無法保證符號完全相等,因而不適合在任何文化特性中顯示。 使用 StringComparison.InvariantCulture 來比較有幾個理由,其中之一是保持已排序的資料在不同的文化特性下顯示時完全相同。 例如,如果包含已排序之顯示識別項清單的大型資料檔案伴隨一個應用程式,則加入至這份清單需要插入非變異樣式排序。

回到頁首

選擇方法呼叫的 StringComparison 成員

下表列出語意字串內容至 StringComparison 列舉成員的對應。

資料

行為

對應的 System.StringComparison

區分大小寫的內部識別項。

XML 和 HTTP 等標準中區分大小寫的識別項。

區分大小寫的安全性相關設定。

位元組完全相符的非語言識別項。

Ordinal

不區分大小寫的內部識別項。

XML 和 HTTP 等標準中不區分大小寫的識別項。

檔案路徑。

登錄機碼和值。

環境變數。

資源識別項 (例如控制代碼名稱)。

不區分大小寫的安全性相關設定。

大小寫不重要的非語言識別項,特別是大部分 Windows 系統服務中儲存的資料。

OrdinalIgnoreCase

某些永續性、語言相關的資料。

需要固定排序次序之語言資料的顯示。

仍與語言相關之不區分文化特性的資料。

InvariantCulture

-或-

InvariantCultureIgnoreCase

向使用者顯示的資料。

大部分的使用者輸入。

需要當地語言自訂的資料。

CurrentCulture

-或-

CurrentCultureIgnoreCase

回到頁首

.NET Framework 的一般字串比較方法

下列各節描述字串比較最常用的方法。

String.Compare

預設解譯:StringComparison.CurrentCulture

由於是字串解譯最主要的作業,這些方法呼叫的所有執行個體都應該經過檢查,以決定字串應該根據目前文化特性來解譯,還是與文化特性分開 (符號形式)。 通常是後者,所以應該改用 StringComparison.Ordinal 比較。

CultureInfo.CompareInfo 屬性傳回的 System.Globalization.CompareInfo 類別也包含 Compare 方法,這個方法透過 CompareOptions 旗標列舉,提供大量比對選項 (序數、忽略空白字元、忽略假名類型等)。

String.CompareTo

預設解譯:StringComparison.CurrentCulture

這個方法目前未提供任何指定 StringComparison 型別的多載。 通常可以將這個方法轉換成建議的 String.Compare(String, String, StringComparison) 形式。

實作 IComparableIComparable<T> 介面的型別會實作這個方法。 因為它沒有提供 StringComparison 參數的選項,所以實作這些型別通常可讓使用者在建構函式中指定 StringComparer。 下列範例定義 FileName 類別,該類別的類別建構函式包含 StringComparer 參數。 然後在 FileName.CompareTo 方法中使用這個 StringComparer 物件。

Public Class FileName : Implements IComparable
   Dim fname As String
   Dim comparer As StringComparer 

   Public Sub New(name As String, comparer As StringComparer)
      If String.IsNullOrEmpty(name) Then
         Throw New ArgumentNullException("name")
      End If

      Me.fname = name

      If comparer IsNot Nothing Then
         Me.comparer = comparer
      Else
         Me.comparer = StringComparer.OrdinalIgnoreCase
      End If      
   End Sub

   Public ReadOnly Property Name As String
      Get
         Return fname
      End Get   
   End Property

   Public Function CompareTo(obj As Object) As Integer _
          Implements IComparable.CompareTo
      If obj Is Nothing Then Return 1

      If Not TypeOf obj Is FileName Then
         obj = obj.ToString()
      Else
         obj = CType(obj, FileName).Name
      End If         
      Return comparer.Compare(Me.fname, obj)
   End Function
End Class
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

預設解譯:StringComparison.Ordinal

String 類別可讓您呼叫靜態或執行個體 Equals 方法多載,或使用靜態等號比較運算子,以測試是否相等。 多載和運算子預設會使用序數比較。 然而,即使您想要執行序數比較,我們還是建議您呼叫明確指定 StringComparison 型別的多載,這樣可以很輕鬆在程式碼中搜尋特定的字串解譯。

String.ToUpper 和 String.ToLower

預設解譯:StringComparison.CurrentCulture

請小心使用這些方法,因為強制將字串轉換為大寫或小寫,通常是做為比較不區分大小寫字串時的輕微正規化。 如果是這樣,請考慮使用不區分大小寫的比較。

也有 String.ToUpperInvariantString.ToLowerInvariant 方法可用。 ToUpperInvariant 是將大小寫正規化的標準方式。 使用 StringComparison.OrdinalIgnoreCase 進行的比較在行為上由兩次呼叫所構成:在兩個字串引數上都呼叫 ToUpperInvariant,然後使用 StringComparison.Ordinal 進行比較。

特定文化特性中,也可以使用多載將表示該文化特性的 CultureInfo 物件傳遞至這個方法,以轉換為大寫和小寫。

Char.ToUpper 和 Char.ToLower

預設解譯:StringComparison.CurrentCulture

這些方法的運作類似於上一節描述的 String.ToUpperString.ToLower 方法。

String.StartsWith 和 String.EndsWith

預設解譯:StringComparison.CurrentCulture

根據預設,這兩個方法都會執行區分文化特性的比較。

String.IndexOf 和 String.LastIndexOf

預設解譯:StringComparison.CurrentCulture

這些方法的預設多載在執行比較時,作法並不一致。 所有包含 Char 參數的 String.IndexOfString.LastIndexOf 方法都執行序數比較,但包含 String 參數的預設 String.IndexOfString.LastIndexOf 方法會執行區分文化特性的比較。

如果您呼叫 String.IndexOf(String)String.LastIndexOf(String) 方法,並將要在目前執行個體中尋找的字串傳遞至這個方法,我們建議您呼叫明確指定 StringComparison 型別的多載。 包含 Char 引數的多載不允許您指定 StringComparison 型別。

回到頁首

間接執行字串比較的方法

有一些以字串比較為主要作業的非字串方法會使用 StringComparer 型別。 StringComparer 類別包含六個靜態屬性,這些屬性會傳回 StringComparer 執行個體,而這些執行個體的 StringComparer.Compare 方法可以執行下列類型的字串比較:

Array.Sort 和 Array.BinarySearch

預設解譯:StringComparison.CurrentCulture

當您將任何資料儲存在集合中時,或從檔案或資料庫讀取保存的資料放到集合時,切換目前文化特性會使集合中的非變異失效。 Array.BinarySearch 方法假設要搜尋之陣列中的項目已排序。 若要排序陣列中的任何字串項目,Array.Sort 方法會呼叫 String.Compare 方法來排序個別項目。 從排序陣列時到搜尋其內容之間,如果文化特性變更,則使用區分文化特性的比較子可能會有危險。 例如,在下列程式碼中,儲存和擷取作業在 Thread.CurrentThread.CurrentCulture 屬性所隱含提供的比較子上執行。 如果文化特性在呼叫 StoreNames 和 DoesNameExist 之間可能變更,尤其是如果陣列內容在這兩個方法呼叫之間保存在某處,則二進位搜尋可能會失敗。

' Incorrect.
Dim storedNames() As String

Public Sub StoreNames(names() As String)
   Dim index As Integer = 0
   ReDim storedNames(names.Length - 1)

   For Each name As String In names
      Me.storedNames(index) = name
      index+= 1
   Next

   Array.Sort(names)          ' Line A.
End Sub

Public Function DoesNameExist(name As String) As Boolean
   Return Array.BinarySearch(Me.storedNames, name) >= 0      ' Line B.
End Function
// 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.
}

下列範例中顯示建議的變化,其中使用相同的序數 (不區分文化特性) 比較方法來排序和搜尋陣列。 在兩個範例中,標記為 Line A 和 Line B 的行中反映變更程式碼。

' Correct.
Dim storedNames() As String

Public Sub StoreNames(names() As String)
   Dim index As Integer = 0
   ReDim storedNames(names.Length - 1)

   For Each name As String In names
      Me.storedNames(index) = name
      index+= 1
   Next

   Array.Sort(names, StringComparer.Ordinal)           ' Line A.
End Sub

Public Function DoesNameExist(name As String) As Boolean
   Return Array.BinarySearch(Me.storedNames, name, StringComparer.Ordinal) >= 0      ' Line B.
End Function
// 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.
}

如果跨文化特性來保存和移動這項資料,且使用排序向使用者呈現這項資料,則您可以考慮使用 StringComparison.InvariantCulture,這在語言上的表現會提供較佳的使用者輸出,且不受未來文化特性變更所影響。 下列範例修改前兩個範例,改用不因國別而異的文化特性來排序和搜尋陣列。

' Correct.
Dim storedNames() As String

Public Sub StoreNames(names() As String)
   Dim index As Integer = 0
   ReDim storedNames(names.Length - 1)

   For Each name As String In names
      Me.storedNames(index) = name
      index+= 1
   Next

   Array.Sort(names, StringComparer.InvariantCulture)           ' Line A.
End Sub

Public Function DoesNameExist(name As String) As Boolean
   Return Array.BinarySearch(Me.storedNames, name, StringComparer.InvariantCulture) >= 0      ' Line B.
End Function
// 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.
}

集合範例:Hashtable 建構函式

雜湊字串提供由字串比較方式而影響作業的第二個範例。

下列範例將 StringComparer.OrdinalIgnoreCase 屬性所傳回的 StringComparer 物件傳遞至 Hashtable 物件,以執行個體化後面這個物件。 因為衍生自 StringComparer 的類別 StringComparer 實作 IEqualityComparer 介面,所以其 GetHashCode 方法會用於計算雜湊資料表中之字串的雜湊程式碼。

Const initialTableCapacity As Integer = 100
Dim h As Hashtable

Public Sub PopulateFileTable(dir As String)
   h = New Hashtable(initialTableCapacity, _
                     StringComparer.OrdinalIgnoreCase)

   For Each filename As String In Directory.GetFiles(dir)
      h.Add(filename, File.GetCreationTime(filename))
   Next                        
End Sub

Public Sub PrintCreationTime(targetFile As String)
   Dim dt As Object = h(targetFile)
   If dt IsNot Nothing Then
      Console.WriteLine("File {0} was created at {1}.", _
         targetFile, _
         CDate(dt))
   Else
      Console.WriteLine("File {0} does not exist.", targetFile)
   End If
End Sub  
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);
   }
}

回到頁首