匯出 (0) 列印
全部展開
本文章是由機器翻譯。 將指標移到文章內的文字上方即可查看原文。 其他資訊。
譯文
原文

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

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

本主題討論 .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

    
    string protocol = GetProtocol(url);       
    if (String.Equals(protocol, "http")) {
       // ...Code to handle HTTP protocol.
    }
    else {
       throw new InvalidOperationException();
    }
    
    
    

一般而言,我們建議您呼叫不依賴預設值的方法,因為這樣使得程式碼用途很明確。 此外,這還可以更易於理解程式碼,也更容易偵錯和維護。 下列範例顯示由先前範例所引起的問題。 它清楚使用序數比較,而差異以免被忽略。


string protocol = GetProtocol(url);       
if (String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) {
   // ...Code to handle HTTP protocol.
}
else {
   throw new InvalidOperationException();
}


回到頁首

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

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

Dd465121.collapse_all(zh-tw,VS.110).gif使用目前文化特性的字串比較

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

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


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" 嘗試執行不區分大小寫的比較所示。


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 static bool IsFileURI(String path) 
{
   return path.StartsWith("FILE:", true, null);
}


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


public static bool IsFileURI(string path) 
{
   return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
}


Dd465121.collapse_all(zh-tw,VS.110).gif序數字串作業

在方法呼叫中指定 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 字元),並且顯示這兩個字串如何被視為相等。


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


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


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


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


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


這些比較還是非常快。

注意事項 注意事項

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

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

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

Dd465121.collapse_all(zh-tw,VS.110).gif使用不因文化特性而異的字串作業

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

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

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

InvariantCulture: a + ̊ = å

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


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 列舉成員的對應。

資料

行為

對應的 System.StringComparison

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

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

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

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

Ordinal

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

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

檔案路徑。

登錄機碼和值。

環境變數。

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

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

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

OrdinalIgnoreCase

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

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

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

InvariantCulture

-或-

InvariantCultureIgnoreCase

向使用者顯示的資料。

大部分的使用者輸入。

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

CurrentCulture

-或-

CurrentCultureIgnoreCase

回到頁首

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

Dd465121.collapse_all(zh-tw,VS.110).gifString.Compare

預設解譯:StringComparison.CurrentCulture

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

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

Dd465121.collapse_all(zh-tw,VS.110).gifString.CompareTo

預設解譯:StringComparison.CurrentCulture

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

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


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);
   }
}


Dd465121.collapse_all(zh-tw,VS.110).gifString.Equals

預設解譯:StringComparison.Ordinal

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

Dd465121.collapse_all(zh-tw,VS.110).gifString.ToUpper 和 String.ToLower

預設解譯:StringComparison.CurrentCulture

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

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

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

Dd465121.collapse_all(zh-tw,VS.110).gifChar.ToUpper 和 Char.ToLower

預設解譯:StringComparison.CurrentCulture

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

Dd465121.collapse_all(zh-tw,VS.110).gifString.StartsWith 和 String.EndsWith

預設解譯:StringComparison.CurrentCulture

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

Dd465121.collapse_all(zh-tw,VS.110).gifString.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 方法可以執行下列類型的字串比較:

Dd465121.collapse_all(zh-tw,VS.110).gifArray.Sort 和 Array.BinarySearch

預設解譯:StringComparison.CurrentCulture

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


// 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 ALine 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.
}


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


// 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.
}


Dd465121.collapse_all(zh-tw,VS.110).gif集合範例:Hashtable 建構函式

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

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


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);
   }
}


回到頁首

當您以非字串資料 (例如數字、日期和時間) 給使用者時,您可以使用使用者的文化特性設定。 根據預設, String.Format 方法和數字型別及日期和時間型別的方法 ToString 提供格式化作業會使用目前執行緒文化特性。 明確指定格式化方法應該使用目前的文化特性,您可以呼叫具有 provider 參數,例如 String.Format(IFormatProvider, String, Object[])DateTime.ToString(IFormatProvider)並將它 CultureInfo.CurrentCulture 屬性格式化方法的多載。

您可以保存非字串資料當做二進位資料或做為格式化資料。 如果您選擇將它儲存為格式化資料,您應該呼叫包含 provider 參數的格式化方法多載以及傳遞 CultureInfo.InvariantCulture 屬性。 不因文化特性而異的文化特性是與電腦無關的格式化資料提供統一的格式。 相反地,來格式化的文化特性而異的文化特性以外的保存資料有一些限制:

  • 有不同的文化特性的系統擷取,或如果目前系統的使用者變更目前的文化特性和嘗試擷取資料,資料可能無法使用。

  • 特定文化特性的屬性會在特定電腦上可以有不同標準值。 任何時間,使用者可以自訂文化特性的顯示設定。 因此,在系統上儲存的格式化資料在使用者自訂的文化特性設定之後可能無法讀取。 格式化資料的跨電腦可攜性可能會被限制。

  • 國際位置,或由格式化數字或日期和時間的國際標準隨時間而變更和這些變更合併至 Windows 作業系統中更新。 在格式化慣例變更時,格式化先前的資料可能會變得無法讀取。

下列範例說明該限制的可攜性從使用文化特性的格式化結果來保存資料。 這個範例會將陣列中的日期和時間值存為檔案。 您可以使用英文 (美國) 文化特性的慣例格式化。 在應用程式變更目前執行緒文化特性變更為法文 (瑞士) 之後,您可以使用目前文化特性的格式化慣例,它會嘗試讀取已儲存的值。 這次嘗試讀取兩資料項目會擲回例外狀況, FormatException ,有些日期現在包含具有 MinValue相等的兩個無效的項目。


using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;

public class Example
{
   private static string filename = @".\dates.dat";

   public static void Main()
   {
      DateTime[] dates = { new DateTime(1758, 5, 6, 21, 26, 0), 
                           new DateTime(1818, 5, 5, 7, 19, 0), 
                           new DateTime(1870, 4, 22, 23, 54, 0),  
                           new DateTime(1890, 9, 8, 6, 47, 0), 
                           new DateTime(1905, 2, 18, 15, 12, 0) }; 
      // Write the data to a file using the current culture.
      WriteData(dates);
      // Change the current culture.
      Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-CH");
      // Read the data using the current culture.
      DateTime[] newDates = ReadData();
      foreach (var newDate in newDates)
         Console.WriteLine(newDate.ToString("g"));
   }

   private static void WriteData(DateTime[] dates) 
   {
      StreamWriter sw = new StreamWriter(filename, false, Encoding.UTF8);    
      for (int ctr = 0; ctr < dates.Length; ctr++) {
         sw.Write("{0}", dates[ctr].ToString("g", CultureInfo.CurrentCulture));
         if (ctr < dates.Length - 1) sw.Write("|");   
      }      
      sw.Close();
   }

   private static DateTime[] ReadData() 
   {
      bool exceptionOccurred = false;

      // Read file contents as a single string, then split it.
      StreamReader sr = new StreamReader(filename, Encoding.UTF8);
      string output = sr.ReadToEnd();
      sr.Close();   

      string[] values = output.Split( new char[] { '|' } );
      DateTime[] newDates = new DateTime[values.Length]; 
      for (int ctr = 0; ctr < values.Length; ctr++) {
         try {
            newDates[ctr] = DateTime.Parse(values[ctr], CultureInfo.CurrentCulture);
         }
         catch (FormatException) {
            Console.WriteLine("Failed to parse {0}", values[ctr]);
            exceptionOccurred = true;
         }
      }      
      if (exceptionOccurred) Console.WriteLine();
      return newDates;
   }
}
// The example displays the following output:
//       Failed to parse 4/22/1870 11:54 PM
//       Failed to parse 2/18/1905 3:12 PM
//       
//       05.06.1758 21:26
//       05.05.1818 07:19
//       01.01.0001 00:00
//       09.08.1890 06:47
//       01.01.0001 00:00
//       01.01.0001 00:00


不過,如果您在呼叫DateTime.ToString(String, IFormatProvider)DateTime.Parse(String, IFormatProvider)CultureInfo.InvariantCulture 取代 CultureInfo.CurrentCulture,保存的資料和時間資料會還原成功,如下列輸出所示。


06.05.1758 21:26
05.05.1818 07:19
22.04.1870 23:54
08.09.1890 06:47
18.02.1905 15:12

回到頁首

社群新增項目

新增
顯示:
© 2014 Microsoft