有关使用 .NET 中字符串的最佳做法Best Practices for Using Strings in .NET

.NET 为开发本地化和全球化应用程序提供广泛支持,在执行排序和显示字符串等常见操作时,轻松应用当前区域性或特定区域性的约定。.NET provides extensive support for developing localized and globalized applications, and makes it easy to apply the conventions of either the current culture or a specific culture when performing common operations such as sorting and displaying strings. 但排序或比较字符串并不总是区分区域性的操作。But sorting or comparing strings is not always a culture-sensitive operation. 例如,对于应用程序内部使用的字符串,通常应该跨所有区域性以相同的方式对其进行处理。For example, strings that are used internally by an application typically should be handled identically across all cultures. 如果将 XML 标记、HTML 标记、用户名、文件路径和系统对象名称等与区域性无关的字符串数据解释为区分区域性,则应用程序代码会遭遇细微的错误、不佳的性能,在某些情况下,还会遭遇安全性问题。When culturally independent string data, such as XML tags, HTML tags, user names, file paths, and the names of system objects, are interpreted as if they were culture-sensitive, application code can be subject to subtle bugs, poor performance, and, in some cases, security issues.

本主题介绍了 .NET 中的字符串排序、比较和大小写转换方法,提出了有关如何选择适当的字符串处理方法的建议,以及有关字符串处理方法的其他信息。This topic examines the string sorting, comparison, and casing methods in .NET, presents recommendations for selecting an appropriate string-handling method, and provides additional information about string-handling methods. 它还讨论如何处理数据格式(如数字数据以及日期和时间数据)以用于显示和存储。It also examines how formatted data, such as numeric data and date and time data, is handled for display and for storage.

对字符串用法的建议Recommendations for string usage

使用 .NET 进行开发时,请遵循以下简要建议使用字符串:When you develop with .NET, follow these simple recommendations when you use strings:

使用字符串时,请避免采用以下做法:Avoid the following practices when you use strings:

  • 不要使用未显式或隐式为字符串操作指定字符串比较规则的重载。Do not use overloads that do not explicitly or implicitly specify the string comparison rules for string operations.
  • 在大多数情况下,不要使用基于 StringComparison.InvariantCulture 的字符串操作。Do not use string operations based on StringComparison.InvariantCulture in most cases. 其中的一个少数例外情况是,保存在语言上有意义但区域性不明确的数据。One of the few exceptions is when you are persisting linguistically meaningful but culturally agnostic data.
  • 不要使用 String.CompareCompareTo 方法的重载和用于确定两个字符串是否相等的返回值为 0 的测试。Do not use an overload of the String.Compare or CompareTo method and test for a return value of zero to determine whether two strings are equal.
  • 不要使用区分区域性格式以字符串形式来保存数值数据或日期和时间数据。Do not use culture-sensitive formatting to persist numeric data or date and time data in string form.

显式指定字符串比较Specifying string comparisons explicitly

重载 .NET 中大部分字符串操作方法。Most of the string manipulation methods in .NET are overloaded. 通常,一个或多个重载会接受默认设置,然而其他重载则不接受默认设置,而是定义比较或操作字符串的精确方式。Typically, one or more overloads accept default settings, whereas others accept no defaults and instead define the precise way in which strings are to be compared or manipulated. 大多数不依赖于默认设置的方法都包括 StringComparison类型的参数,该参数是按区域性和大小写为字符串比较显式指定规则的枚举。Most of the methods that do not rely on defaults include a parameter of type StringComparison, which is an enumeration that explicitly specifies rules for string comparison by culture and case. 下表描述 StringComparison 枚举成员。The following table describes the StringComparison enumeration members.

StringComparison 成员StringComparison member 说明Description
CurrentCulture 使用当前区域性执行区分大小写的比较。Performs a case-sensitive comparison using the current culture.
CurrentCultureIgnoreCase 使用当前区域性执行不区分大小写的比较。Performs a case-insensitive comparison using the current culture.
InvariantCulture 使用固定区域性执行区分大小写的比较。Performs a case-sensitive comparison using the invariant culture.
InvariantCultureIgnoreCase 使用固定区域性执行不区分大小写的比较。Performs a case-insensitive comparison using the invariant culture.
Ordinal 执行序号比较。Performs an ordinal comparison.
OrdinalIgnoreCase 执行不区分大小写的序号比较。Performs a case-insensitive ordinal comparison.

例如, IndexOf 方法(它返回 String 对象中与某字符或字符串匹配的子字符串的索引)具有九种重载:For example, the IndexOf method, which returns the index of a substring in a String object that matches either a character or a string, has nine overloads:

我们建议选择不使用默认值的重载,原因如下:We recommend that you select an overload that does not use default values, for the following reasons:

  • 具有默认参数的一些重载(在字符串实例中搜索 Char 的重载)执行序号比较,而其他重载(在字符串实例中搜索字符串的重载)执行的是区分区域性的比较。Some overloads with default parameters (those that search for a Char in the string instance) perform an ordinal comparison, whereas others (those that search for a string in the string instance) are culture-sensitive. 要记住哪种方法使用哪个默认值并非易事,并很容易混淆重载。It is difficult to remember which method uses which default value, and easy to confuse the overloads.

  • 依赖于方法调用默认值的代码的意图并不清楚。The intent of the code that relies on default values for method calls is not clear. 在下面依赖于默认值的示例中,很难了解开发人员对两个字符串的实际意图是执行序号比较还是语言比较,或者 protocol 和“http”之间存在的大小写差异是否会导致相等性测试返回 false类型的参数的方法重载。In the following example, which relies on defaults, it is difficult to know whether the developer actually intended an ordinal or a linguistic comparison of two strings, or whether a case difference between protocol and "http" might cause the test for equality to return false.

    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") Then
       ' ...Code to handle HTTP protocol.
    Else
       Throw New InvalidOperationException()
    End If   
    

一般情况下,我们建议调用不依赖于默认设置的方法,因为这会明确代码的意图。In general, we recommend that you call a method that does not rely on defaults, because it makes the intent of the code unambiguous. 这进而使代码更具可读性且更易于调试和维护。This, in turn, makes the code more readable and easier to debug and maintain. 下面的示例解决了前面示例中提出的问题。The following example addresses the questions raised about the previous example. 使用序号比较并且忽略大小写差异。It makes it clear that ordinal comparison is used and that differences in case are ignored.

string protocol = GetProtocol(url);       
if (String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) {
   // ...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   

字符串比较的详细信息The details of string comparison

字符串比较是许多字符串相关操作的核心,特别是排序和相等性测试操作。String comparison is the heart of many string-related operations, particularly sorting and testing for equality. 字符串以确定的顺序进行排序:如果在排序的字符串列表中,“my”出现在“string”之前,则“my”必定小于或等于“string”。Strings sort in a determined order: If "my" appears before "string" in a sorted list of strings, "my" must compare less than or equal to "string". 此外,比较可隐式确定相等性。Additionally, comparison implicitly defines equality. 对于认为是相等的字符串,比较操作将返回零。The comparison operation returns zero for strings it deems equal. 对此很好的解释是两个字符串都不小于对方。A good interpretation is that neither string is less than the other. 涉及到字符串的最有意义的操作包括这些步骤中的一个或两个步骤:与另一个字符串进行比较和执行明确的排序操作。Most meaningful operations involving strings include one or both of these procedures: comparing with another string, and executing a well-defined sort operation.

备注

可以下载排序权重表,这是一组文本文件,其中包含有关 Windows 操作系统排序和比较操作中所使用的字符权重的信息,也可以下载默认 Unicode 排序元素表,这是适用于 Linux 和 macOS 的最新版排序权重表。You can download the Sorting Weight Tables, a set of text files that contain information on the character weights used in sorting and comparison operations for Windows operating systems, and the Default Unicode Collation Element Table, the latest version of the sort weight table for Linux and macOS. Linux 和 macOS 上的特定排序权重表版本取决于系统上安装的 International Components for Unicode 库的版本。The specific version of the sort weight table on Linux and macOS depends on the version of the International Components for Unicode libraries installed on the system. 有关 ICU 版本及它们所实现的 Unicode 版本的信息,请参阅下载 ICUFor information on ICU versions and the Unicode versions that they implement, see Downloading ICU.

但是,评估两个字符串的相等性或排序顺序不会生成一个正确的结果;其结果取决于用于比较这两个字符串的条件。However, evaluating two strings for equality or sort order does not yield a single, correct result; the outcome depends on the criteria used to compare the strings. 特别是,序号或基于当前区域性或固定区域性(基于英语语言的区域设置不明确的区域性)的大小写和排序约定的字符串比较可能会产生不同的结果。In particular, string comparisons that are ordinal or that are based on the casing and sorting conventions of the current culture or the invariant culture (a locale-agnostic culture based on the English language) may produce different results.

此外,使用不同 .NET 版本或在不同操作系统或不同的操作系统版本上使用 .NET 进行字符串比较时,返回的结果可能不同。In addition, string comparisons using different versions of .NET or using .NET on different operating systems or operating system versions may return different results. 有关详细信息,请参阅字符串和 Unicode 标准For more information, see Strings and the Unicode Standard.

使用当前区域性的字符串比较String comparisons that use the current culture

一个条件涉及在比较字符串时使用当前区域性的约定。One criterion involves using the conventions of the current culture when comparing strings. 基于当前区域性的比较使用线程的当前区域性或区域设置。Comparisons that are based on the current culture use the thread's current culture or locale. 如果用户未设置该区域性,则默认为“控制面板”中“区域选项” 窗口中的设置。If the culture is not set by the user, it defaults to the setting in the Regional Options window in Control Panel. 当数据与语言相关并反映区分区域性的用户交互时,应始终使用基于当前区域性的比较。You should always use comparisons that are based on the current culture when data is linguistically relevant, and when it reflects culture-sensitive user interaction.

但是,当区域性发生更改时,.NET 中的比较和大小写行为也发生更改。However, comparison and casing behavior in .NET changes when the culture changes. 如果执行应用程序的计算机与用于开发该应用程序的计算机具有不同的区域性,或者执行线程改变它的区域性,则会发生这种情况。This happens when an application executes on a computer that has a different culture than the computer on which the application was developed, or when the executing thread changes its culture. 此行为是有意而为之的,但许多开发人员不易察觉此行为。This behavior is intentional, but it remains non-obvious to many developers. 下面的示例说明了美国英语(“en-US”)与瑞典语(“sv-SE”)区域性在排序顺序中的差异。The following example illustrates differences in sort order between the U.S. English ("en-US") and Swedish ("sv-SE") cultures. 请注意,单词“ångström”、“Windows”和“Visual Studio”将出现在已排序的字符串数组的不同位置。Note that the words "ångström", "Windows", and "Visual Studio" appear in different positions in the sorted string arrays.

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

使用当前区域性的不区分大小写比较和区分区域性的比较是相同的,只不过前者忽略由线程的当前区域性指示的大小写。Case-insensitive comparisons that use the current culture are the same as culture-sensitive comparisons, except that they ignore case as dictated by the thread's current culture. 这种情况也可表明它的排序顺序。This behavior may manifest itself in sort orders as well.

以下方法默认利用使用当前区域性语义的比较:Comparisons that use current culture semantics are the default for the following methods:

总之,我们建议调用具有 StringComparison 参数的重载,以便明确方法调用的意图。In any case, we recommend that you call an overload that has a StringComparison parameter to make the intent of the method call clear.

当从语言角度解释非语言的字符串数据,或利用其他区域性的约定解释某个特定区域性中的字符串时,则会发生或大或小的错误。Subtle and not so subtle bugs can emerge when non-linguistic string data is interpreted linguistically, or when string data from a particular culture is interpreted using the conventions of another culture. 土耳其语 I 问题便是一个规范示例。The canonical example is the Turkish-I problem.

对于几乎所有拉丁字母来讲(包括美国英语),字符“i”(\u0069) 是字符“I”(\u0049) 的小写形式。For nearly all Latin alphabets, including U.S. English, the character "i" (\u0069) is the lowercase version of the character "I" (\u0049). 此大小写规则快速成为在此类区域性中编程的人员的默认设置。This casing rule quickly becomes the default for someone programming in such a culture. 但是,土耳其语(“tr-TR”)字母表中包含一个“带有点的 I”的字符“İ”(\u0130),该字符是“i”的大写形式。However, the Turkish ("tr-TR") alphabet includes an "I with a dot" character "İ" (\u0130), which is the capital version of "i". 土耳其语还包括一个小写“不带点的 i”字符,即为“ı”(\u0131),该字符的大写形式为“I”。Turkish also includes a lowercase "i without a dot" character, "ı" (\u0131), which capitalizes to "I". 阿塞拜疆语(“az”)区域也会出现这种情况。This behavior occurs in the Azerbaijani ("az") culture as well.

因此,关于将“i”变为大写或将“I”变为小写的假设并非在所有区域性中都是有效的。Therefore, assumptions made about capitalizing "i" or lowercasing "I" are not valid among all cultures. 如果为字符串比较例程使用默认重载,则它们可能会因区域性不同而异。If you use the default overloads for string comparison routines, they will be subject to variance between cultures. 如果对非语言的数据进行比较,使用默认重载会产生不良后果,如以下对字符串“file”和“FILE”执行不区分大小写的比较尝试所示。If the data to be compared is non-linguistic, using the default overloads can produce undesirable results, as the following attempt to perform a case-insensitive comparison of the strings "file" and "FILE" illustrates.

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

如果无意中在安全敏感设置中使用了区域性,则此比较会导致发生重大问题,如以下示例所示。This comparison could cause significant problems if the culture is inadvertently used in security-sensitive settings, as in the following example. 如果当前区域性为美国英语,则 IsFileURI("file:") 等方法调用将返回 true;但如果当前区域性为土耳其语,则将返回 falseA method call such as IsFileURI("file:") returns true if the current culture is U.S. English, but false if the current culture is Turkish. 因此,在土耳其语系统中,有人可能会避开阻止访问以“FILE:”开头的不区分大小写的安全措施。Thus, on Turkish systems, someone could circumvent security measures that block access to case-insensitive URIs that begin with "FILE:".

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

在这种情况下,由于“file:”会被解释为非语言的、不区分区域性的标识符,因此,应按照下面的示例所示编写代码:In this case, because "file:" is meant to be interpreted as a non-linguistic, culture-insensitive identifier, the code should instead be written as shown in the following example:

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

序号字符串操作Ordinal string operations

在方法调用中指定 StringComparison.OrdinalStringComparison.OrdinalIgnoreCase 值表示非语言比较,这种比较忽略了自然语言的特性。Specifying the StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase value in a method call signifies a non-linguistic comparison in which the features of natural languages are ignored. 利用 StringComparison 值调用的方法将字符串操作决策建立在简单的字节比较的基础之上,而不是按区域性参数化的大小写或相等表。Methods that are invoked with these StringComparison values base string operation decisions on simple byte comparisons instead of casing or equivalence tables that are parameterized by culture. 在大多数情况下,这种方法最符合字符串的预期解释,并使代码更快更可靠。In most cases, this approach best fits the intended interpretation of strings while making code faster and more reliable.

序号比较就是字符串比较,在这种比较中,将比较每个字符串中的每个字节且不进行语言解释;例如,“windows”不匹配“Windows”。Ordinal comparisons are string comparisons in which each byte of each string is compared without linguistic interpretation; for example, "windows" does not match "Windows". 实质上,这是对 C 运行时 strcmp 函数的调用。This is essentially a call to the C runtime strcmp function. 当上下文指示应完全匹配字符串或要求保守匹配策略时,请使用这种比较。Use this comparison when the context dictates that strings should be matched exactly or demands conservative matching policy. 此外,序号比较是最快的比较操作,因为它在确定结果时不应用任何语言规则。Additionally, ordinal comparison is the fastest comparison operation because it applies no linguistic rules when determining a result.

.NET 中的字符串可以包括嵌入的空字符。Strings in .NET can contain embedded null characters. 序号比较与区分区域性的比较(包括使用固定区域性的比较)之间最明显的区别之一是对字符串中嵌入的空字符的处理方式。One of the clearest differences between ordinal and culture-sensitive comparison (including comparisons that use the invariant culture) concerns the handling of embedded null characters in a string. 当使用 String.CompareString.Equals 方法执行区分区域性的比较(包括使用固定区域性的比较)时,将忽略这些字符。These characters are ignored when you use the String.Compare and String.Equals methods to perform culture-sensitive comparisons (including comparisons that use the invariant culture). 因此,在区分区域性的比较中,包含嵌入的空字符的字符串可视为等于不包含空字符的字符串。As a result, in culture-sensitive comparisons, strings that contain embedded null characters can be considered equal to strings that do not.

重要

尽管字符串比较方法忽略嵌入的空字符,但是 String.ContainsString.EndsWithString.IndexOfString.LastIndexOfString.StartsWith 等字符串搜索方法并不会忽略这些字符。Although string comparison methods disregard embedded null characters, string search methods such as String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOf, and String.StartsWith do not.

下面的示例对字符串“Aa”与在“A”和“a”之间嵌入了多个空字符的相似字符串进行区分区域性的比较,并显示如何将这两个字符串视为相等的字符串:The following example performs a culture-sensitive comparison of the string "Aa" with a similar string that contains several embedded null characters between "A" and "a", and shows how the two strings are considered equal:

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

但是,当使用序号比较时,这两个字符串不会视为相等,如下面的示例所示:However, the strings are not considered equal when you use ordinal comparison, as the following example shows:

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

不区分大小写的序号比较是第二种最保守的方法。Case-insensitive ordinal comparisons are the next most conservative approach. 这些比较会忽略大多数的大小写;例如,“windows”会匹配“Windows”。These comparisons ignore most casing; for example, "windows" matches "Windows". 在处理 ASCII 字符时,此策略等同于 StringComparison.Ordinal,只不过它会忽略常用的 ASCII 大小写。When dealing with ASCII characters, this policy is equivalent to StringComparison.Ordinal, except that it ignores the usual ASCII casing. 因此,[A, Z] (\u0041-\u005A) 中的任何字符都会匹配 [a,z] (\u0061-\007A) 中的相应字符。Therefore, any character in [A, Z] (\u0041-\u005A) matches the corresponding character in [a,z] (\u0061-\007A). 超出 ASCII 范围的大小写使用固定区域性的表。Casing outside the ASCII range uses the invariant culture's tables. 因此,下面的比较:Therefore, the following comparison:

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

等效于(但会更快)这种比较:is equivalent to (but faster than) this comparison:

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

这些比较仍非常快。These comparisons are still very fast.

备注

文件系统、注册表项和值以及环境变量的字符串行为可由 StringComparison.OrdinalIgnoreCase很好地表现出来。The string behavior of the file system, registry keys and values, and environment variables is best represented by StringComparison.OrdinalIgnoreCase.

StringComparison.OrdinalStringComparison.OrdinalIgnoreCase 均直接使用二进制值并最适合匹配。Both StringComparison.Ordinal and StringComparison.OrdinalIgnoreCase use the binary values directly, and are best suited for matching. 当不确定比较设置时,请使用这两个值中的其中一个。When you are not sure about your comparison settings, use one of these two values. 不过,由于它们执行逐字节比较,因此不会按照语言排序顺序(如英语词典)进行排序,而是按照二进制排序顺序。However, because they perform a byte-by-byte comparison, they do not sort by a linguistic sort order (like an English dictionary) but by a binary sort order. 如果向用户显示结果,则在大多数上下文中结果都看上去不正常。The results may look odd in most contexts if displayed to users.

序号语义是不包括 String.Equals 参数(包括相等运算符)的 StringComparison 重载的默认项。Ordinal semantics are the default for String.Equals overloads that do not include a StringComparison argument (including the equality operator). 总之,我们建议调用具有 StringComparison 参数的重载。In any case, we recommend that you call an overload that has a StringComparison parameter.

使用固定区域性的字符串操作string operations that use the invariant culture

具有固定区域性的比较使用由静态 CompareInfo 属性返回的 CultureInfo.InvariantCulture 属性。Comparisons with the invariant culture use the CompareInfo property returned by the static CultureInfo.InvariantCulture property. 此行为在所有系统中都相同;它会将其范围外的任何字符转换为其认为等效的固定字符。This behavior is the same on all systems; it translates any characters outside its range into what it believes are equivalent invariant characters. 此策略对于在各个区域性中维护一组字符串行为很有用,但经常产生意外的结果。This policy can be useful for maintaining one set of string behavior across cultures, but it often provides unexpected results.

具有固定区域性的不区分大小写的比较也使用由静态 CompareInfo 属性返回的静态 CultureInfo.InvariantCulture 属性以获取比较信息。Case-insensitive comparisons with the invariant culture use the static CompareInfo property returned by the static CultureInfo.InvariantCulture property for comparison information as well. 所转换字符中的任何大小写差异都将被忽略。Any case differences among these translated characters are ignored.

使用 StringComparison.InvariantCultureStringComparison.Ordinal 的比较对 ASCII 字符串产生相同的作用。Comparisons that use StringComparison.InvariantCulture and StringComparison.Ordinal work identically on ASCII strings. 但是, StringComparison.InvariantCulture 会做出可能不适用于解释为一组字节的字符串的语言性决策。However, StringComparison.InvariantCulture makes linguistic decisions that might not be appropriate for strings that have to be interpreted as a set of bytes. 还可以使用 CultureInfo.InvariantCulture.CompareInfo 对象使 Compare 方法将一组特定的字符解释为等效字符。The CultureInfo.InvariantCulture.CompareInfo object makes the Compare method interpret certain sets of characters as equivalent. 例如,下面的等效字符在固定区域性中是有效的:For example, the following equivalence is valid under the invariant culture:

InvariantCulture: a + ̊ = åInvariantCulture: a + ̊ = å

如果 A 字符的小写拉丁字母“a”(\u0061) 旁边有上方组合圆圈字符“+ " ̊”(\u030a),A 字符就会被解释为,上方带有圆圈的小写拉丁字母“å”(\u00e5)。The LATIN SMALL LETTER A character "a" (\u0061), when it is next to the COMBINING RING ABOVE character "+ " ̊" (\u030a), is interpreted as the LATIN SMALL LETTER A WITH RING ABOVE character "å" (\u00e5). 如下面的示例所示,此行为不同于序号比较。As the following example shows, this behavior differs from ordinal comparison.

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

当解释其中出现如“å”组合的文件名称、cookie 或其他内容时,序号比较仍会提供最透明和最合适的行为。When interpreting file names, cookies, or anything else where a combination such as "å" can appear, ordinal comparisons still offer the most transparent and fitting behavior.

总的来说,固定区域性具有极少的对比较有用的属性。On balance, the invariant culture has very few properties that make it useful for comparison. 它会以与语言相关的方式执行比较,使其无法保证完整的符号等效性,但它并不是任何区域性中显示的选择。It does comparison in a linguistically relevant manner, which prevents it from guaranteeing full symbolic equivalence, but it is not the choice for display in any culture. 使用 StringComparison.InvariantCulture 进行比较的其中一个原因是为多个区域性相同的显示保留已排序的数据。One of the few reasons to use StringComparison.InvariantCulture for comparison is to persist ordered data for a cross-culturally identical display. 例如,如果应用程序附带包含用于显示的已排序标识符列表的大型数据文件,则添加到此列表将需要使用固定条件样式排序插入。For example, if a large data file that contains a list of sorted identifiers for display accompanies an application, adding to this list would require an insertion with invariant-style sorting.

为方法调用选择 StringComparison 成员Choosing a StringComparison member for your method call

下表概述了从语义字符串上下文到 StringComparison 枚举成员的映射:The following table outlines the mapping from semantic string context to a StringComparison enumeration member:

数据Data 行为Behavior 相应 System.StringComparisonCorresponding System.StringComparison

valuevalue
区分大小写的内部标识符。Case-sensitive internal identifiers.

区分大小写的标准标识符(例如 XML 和 HTTP)。Case-sensitive identifiers in standards such as XML and HTTP.

区分大小写的安全相关设置。Case-sensitive security-related settings.
字节完全匹配的非语言标识符。A non-linguistic identifier, where bytes match exactly. Ordinal
不区分大小写的内部标识符。Case-insensitive internal identifiers.

不区分大小写的标准标识符(例如 XML 和 HTTP)。Case-insensitive identifiers in standards such as XML and HTTP.

文件路径。File paths.

注册表项和值。Registry keys and values.

环境变量。Environment variables.

资源标识符(例如,句柄名称)。Resource identifiers (for example, handle names).

不区分大小写的安全相关设置。Case-insensitive security-related settings.
无关大小写的非语言标识符;尤其是存储在大多数 Windows 系统服务中的数据。A non-linguistic identifier, where case is irrelevant; especially data stored in most Windows system services. OrdinalIgnoreCase
某些保留的、与语言相关的数据。Some persisted, linguistically relevant data.

需要固定排序顺序的语言数据的显示。Display of linguistic data that requires a fixed sort order.
仍与语言相关的区域性不明确数据。Culturally agnostic data that still is linguistically relevant. InvariantCulture

-或--or-

InvariantCultureIgnoreCase
向用户显示的数据。Data displayed to the user.

大多数用户输入。Most user input.
需要本地语言自定义的数据。Data that requires local linguistic customs. CurrentCulture

-或--or-

CurrentCultureIgnoreCase

.NET 中的常见字符串比较方法Common string comparison methods in .NET

以下各节介绍最常用于执行字符串比较的方法。The following sections describe the methods that are most commonly used for string comparison.

String.CompareString.Compare

默认解释: StringComparison.CurrentCultureDefault interpretation: StringComparison.CurrentCulture.

作为字符串解释最核心的操作,应根据当前区域性检查这些方法调用的所有实例来确定是否应该从区域性(符号)解释或分离字符串。As the operation most central to string interpretation, all instances of these method calls should be examined to determine whether strings should be interpreted according to the current culture, or dissociated from the culture (symbolically). 通常情况下,采用后者,并且应改用 StringComparison.Ordinal 比较。Typically, it is the latter, and a StringComparison.Ordinal comparison should be used instead.

System.Globalization.CompareInfo 属性返回的 CultureInfo.CompareInfo 类也包括利用 Compare 标记枚举的方式提供大量匹配选项(序号、忽略空白、忽略假名类型等)的 CompareOptions 方法。The System.Globalization.CompareInfo class, which is returned by the CultureInfo.CompareInfo property, also includes a Compare method that provides a large number of matching options (ordinal, ignoring white space, ignoring kana type, and so on) by means of the CompareOptions flag enumeration.

String.CompareToString.CompareTo

默认解释: StringComparison.CurrentCultureDefault interpretation: StringComparison.CurrentCulture.

此方法当前不提供指定 StringComparison 类型的重载。This method does not currently offer an overload that specifies a StringComparison type. 通常可以将此方法转换为建议的 String.Compare(String, String, StringComparison) 形式。It is usually possible to convert this method to the recommended String.Compare(String, String, StringComparison) form.

实现 IComparableIComparable<T> 接口的类型实现此方法。Types that implement the IComparable and IComparable<T> interfaces implement this method. 由于它不提供 StringComparison 参数选项,因此实现类型经常使用户在其构造函数中指定 StringComparerBecause it does not offer the option of a StringComparison parameter, implementing types often let the user specify a StringComparer in their constructor. 下面的示例定义 FileName 类,其类构造函数包括 StringComparer 参数。The following example defines a FileName class whose class constructor includes a StringComparer parameter. 然后此 StringComparer 对象将用于 FileName.CompareTo 方法。This StringComparer object is then used in the FileName.CompareTo method.

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

String.EqualsString.Equals

默认解释: StringComparison.OrdinalDefault interpretation: StringComparison.Ordinal.

String 类可通过调用静态或实例 Equals 方法重载或使用静态相等运算符,测试是否相等。The String class lets you test for equality by calling either the static or instance Equals method overloads, or by using the static equality operator. 默认情况下,重载和运算符使用序号比较。The overloads and operator use ordinal comparison by default. 但是,我们仍然建议调用显式指定 StringComparison 类型的重载,即使想要执行序号比较;这将更轻松地搜索特定字符串解释的代码。However, we still recommend that you call an overload that explicitly specifies the StringComparison type even if you want to perform an ordinal comparison; this makes it easier to search code for a certain string interpretation.

String.ToUpper 和 String.ToLowerString.ToUpper and String.ToLower

默认解释: StringComparison.CurrentCultureDefault interpretation: StringComparison.CurrentCulture.

应谨慎使用这些方法,因为将字符串强制为大写或小写经常用作在不考虑大小写的情况下比较字符串的较小规范化。You should be careful when you use these methods, because forcing a string to a uppercase or lowercase is often used as a small normalization for comparing strings regardless of case. 如果是这样,请考虑使用不区分大小写的比较。If so, consider using a case-insensitive comparison.

还可以使用 String.ToUpperInvariantString.ToLowerInvariant 方法。The String.ToUpperInvariant and String.ToLowerInvariant methods are also available. ToUpperInvariant 是规范化大小写的标准方式。ToUpperInvariant is the standard way to normalize case. 使用 StringComparison.OrdinalIgnoreCase 进行的比较在行为上是两个调用的组合:对两个字符串参数调用 ToUpperInvariant ,并使用 StringComparison.Ordinal执行比较。Comparisons made using StringComparison.OrdinalIgnoreCase are behaviorally the composition of two calls: calling ToUpperInvariant on both string arguments, and doing a comparison using StringComparison.Ordinal.

通过向方法传递表示区域性的 CultureInfo 对象,重载也已可用于转换该特性区域性中的大写和小写字母。Overloads are also available for converting to uppercase and lowercase in a specific culture, by passing a CultureInfo object that represents that culture to the method.

Char.ToUpper 和 Char.ToLowerChar.ToUpper and Char.ToLower

默认解释: StringComparison.CurrentCultureDefault interpretation: StringComparison.CurrentCulture.

这些方法的工作原理类似于上一节中所述的 String.ToUpperString.ToLower 方法。These methods work similarly to the String.ToUpper and String.ToLower methods described in the previous section.

String.StartsWith 和 String.EndsWithString.StartsWith and String.EndsWith

默认解释: StringComparison.CurrentCultureDefault interpretation: StringComparison.CurrentCulture.

默认情况下,这两种方法执行区分区域性的比较。By default, both of these methods perform a culture-sensitive comparison.

String.IndexOf 和 String.LastIndexOfString.IndexOf and String.LastIndexOf

默认解释: StringComparison.CurrentCultureDefault interpretation: StringComparison.CurrentCulture.

这些方法的默认重载如何执行比较方面缺乏一致性。There is a lack of consistency in how the default overloads of these methods perform comparisons. 包含 String.IndexOf 参数的所有 String.LastIndexOfChar 方法都执行序号比较,但是包含 String.IndexOf 参数的默认 String.LastIndexOfString 方法都执行区分区域性的比较。All String.IndexOf and String.LastIndexOf methods that include a Char parameter perform an ordinal comparison, but the default String.IndexOf and String.LastIndexOf methods that include a String parameter perform a culture-sensitive comparison.

如果调用 String.IndexOf(String)String.LastIndexOf(String) 方法并向其传递一个字符串以在当前实例中查找,那么我们建议调用显式指定 StringComparison 类型的重载。If you call the String.IndexOf(String) or String.LastIndexOf(String) method and pass it a string to locate in the current instance, we recommend that you call an overload that explicitly specifies the StringComparison type. 包括 Char 参数的重载不允许指定 StringComparison 类型。The overloads that include a Char argument do not allow you to specify a StringComparison type.

间接执行字符串比较的方法Methods that perform string comparison indirectly

将字符串比较作为核心操作的一些非字符串方法使用 StringComparer 类型。Some non-string methods that have string comparison as a central operation use the StringComparer type. StringComparer 类型包含六个返回 StringComparer 实例的静态属性,这些实例的 StringComparer.Compare 方法可执行以下类型的字符串比较:The StringComparer class includes six static properties that return StringComparer instances whose StringComparer.Compare methods perform the following types of string comparisons:

Array.Sort 和 Array.BinarySearchArray.Sort and Array.BinarySearch

默认解释: StringComparison.CurrentCultureDefault interpretation: StringComparison.CurrentCulture.

当在集合中存储任何数据,或将持久数据从文件或数据库中读取到集合中时,切换当前区域性可能会使集合中的固定条件无效。When you store any data in a collection, or read persisted data from a file or database into a collection, switching the current culture can invalidate the invariants in the collection. Array.BinarySearch 方法假定已对数组中要搜索的元素排序。The Array.BinarySearch method assumes that the elements in the array to be searched are already sorted. 若要对数组中的任何字符串元素进行排序, Array.Sort 方法会调用 String.Compare 方法以对各个元素进行排序。To sort any string element in the array, the Array.Sort method calls the String.Compare method to order individual elements. 如果对数组进行排序和搜索其内容的时间范围内区域性发生变化,那么使用区分区域性的比较器会很危险。Using a culture-sensitive comparer can be dangerous if the culture changes between the time that the array is sorted and its contents are searched. 例如在下面的代码中,是在由 Thread.CurrentThread.CurrentCulture 属性。For example, in the following code, storage and retrieval operate on the comparer that is provided implicitly by the Thread.CurrentThread.CurrentCulture property. 如果在调用 StoreNamesDoesNameExist之间更改了区域性(尤其是数组内容保存在两个方法调用之间的某个位置),那么二进制搜索可能会失败。If the culture can change between the calls to StoreNames and DoesNameExist, and especially if the array contents are persisted somewhere between the two method calls, the binary search may fail.

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

建议的变体将显示在下面使用相同序号(不区分区域性)比较方法进行排序并搜索数组的示例中。A recommended variation appears in the following example, which uses the same ordinal (culture-insensitive) comparison method both to sort and to search the array. 在这两个示例中,更改代码会反映在标记 Line ALine B 的代码行中。The change code is reflected in the lines labeled Line A and Line B in the two examples.

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

如果此数据永久保留并跨区域性移动,并且使用排序来向用户显示此数据,则可以考虑使用 StringComparison.InvariantCulture,其语言操作可获得更好的用户输出且不受区域性更改的影响。If this data is persisted and moved across cultures, and sorting is used to present this data to the user, you might consider using StringComparison.InvariantCulture, which operates linguistically for better user output but is unaffected by changes in culture. 下面的示例修改了前面两个示例,使用固定区域性对数组进行排序和搜索。The following example modifies the two previous examples to use the invariant culture for sorting and searching the array.

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

集合示例:哈希表构造函数Collections example: Hashtable constructor

哈希字符串提供了第二个运算示例,该运算受比较字符串的方式影响。Hashing strings provides a second example of an operation that is affected by the way in which strings are compared.

下面的示例实例化 Hashtable 对象,方法是向其传递由 StringComparer 属性返回的 StringComparer.OrdinalIgnoreCase 对象。The following example instantiates a Hashtable object by passing it the StringComparer object that is returned by the StringComparer.OrdinalIgnoreCase property. 由于派生自 StringComparer 的类 StringComparer 实现 IEqualityComparer 接口,其 GetHashCode 方法用于计算哈希表中的字符串的哈希代码。Because a class StringComparer that is derived from StringComparer implements the IEqualityComparer interface, its GetHashCode method is used to compute the hash code of strings in the hash table.

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

显示和保存有格式的数据Displaying and persisting formatted data

当给用户显示非字符串数据(如数字、日期和时间)时,使用用户的区域性设置来格式化他们。When you display non-string data such as numbers and dates and times to users, format them by using the user's cultural settings. 默认情况下,以下所有内容都在格式设置操作中使用当前线程区域性:By default, the following all use the current thread culture in formatting operations:

  • C#Visual Basic 编译器支持的内插字符串。Interpolated strings supported by the C# and Visual Basic compilers.
  • 字符串串联操作,它使用 C#Visual Basic 串联运算符或直接调用 String.Concat 方法。String concatenation operations that use the C# or Visual Basic concatenation operators or that call the String.Concat method directly.
  • String.Format 方法。The String.Format method.
  • 数值类型的 ToString 方法以及日期和时间类型。The ToString methods of the numeric types and the date and time types.

若要显式指定应使用指定区域性约定或固定区域性设置字符串的格式,可以执行以下操作:To explicitly specify that a string should be formatted by using the conventions of a designated culture or the invariant culture, you can do the following:

  • 当使用 String.FormatToString 方法时,调用具有 provider 参数(如 String.Format(IFormatProvider, String, Object[])DateTime.ToString(IFormatProvider))的重载,并将 CultureInfo.CurrentCulture 属性(表示所需区域性的 CultureInfo 实例)或 CultureInfo.InvariantCulture 属性传递给它。When using the String.Format and ToString methods, call an overload that has a provider parameter, such as String.Format(IFormatProvider, String, Object[]) or DateTime.ToString(IFormatProvider), and pass it the CultureInfo.CurrentCulture property, a CultureInfo instance that represents the desired culture, or the CultureInfo.InvariantCulture property.

  • 对于字符串串联,不允许编译器执行任何隐式转换。For string concatenation, do not allow the compiler to perform any implicit conversions. 可通过调用具有 provider 参数的 ToString 重载来执行显式转换。Instead, perform an explicit conversion by calling a ToString overload that has a provider parameter. 例如,在将 Double 值转换为以下 C# 代码中的字符串时,编译器隐式使用当前区域性:For example, the compiler implicitly uses the current culture when converting a Double value to a string in the following C# code:

    string concat1 = "The amount is " + 126.03 + ".";
    Console.WriteLine(concat1);
    

    可以通过调用 Double.ToString(IFormatProvider) 方法显式指定在转换中使用格式约定的区域性,如下面的 C# 代码所示:Instead, you can explicitly specify the culture whose formatting conventions are used in the conversion by calling the Double.ToString(IFormatProvider) method, as the following C# code does:

    string concat2 = "The amount is " + 126.03.ToString(CultureInfo.InvariantCulture) + ".";
    Console.WriteLine(concat2);
    
  • 对于字符串内插,不是将内插字符串分配给 String 实例,而是将其分配给 FormattableStringFor string interpolation, rather than assigning an interpolated string to a String instance, assign it to a FormattableString. 然后,可以调用其 FormattableString.ToString() 方法生成反映当前区域性约定的结果字符串,也可以调用 FormattableString.ToString(IFormatProvider) 方法生成反映指定区域性约定的结果字符串。You can then call its FormattableString.ToString() method produce a result string that reflects the conventions of the current culture, or you can call the FormattableString.ToString(IFormatProvider) method to produce a result string that reflects the conventions of a specified culture. 还可以将可格式化字符串传递给静态 FormattableString.Invariant 方法,以生成反映固定区域性约定的结果字符串。You can also pass the formattable string to the static FormattableString.Invariant method to produce a result string that reflects the conventions of the invariant culture. 下面的示例阐释了这种方法。The following example illustrates this approach. (该示例的输出反映了当前的 zh-CN 区域性。)(The output from the example reflects a current culture of en-US.)

    using System;
    using System.Globalization;
    
    class Program
    {
        static void Main()
        {
            Decimal value = 126.03m;
            FormattableString amount = $"The amount is {value:C}"; 
            Console.WriteLine(amount.ToString());
            Console.WriteLine(amount.ToString(new CultureInfo("fr-FR")));
            Console.WriteLine(FormattableString.Invariant(amount));
        }
    }
    // The example displays the following output:
    //    The amount is $126.03
    //    The amount is 126,03 €
    //    The amount is ¤126.03
    
    Imports System.Globalization
    
    Module Program
        Sub Main()
            Dim value As Decimal = 126.03
            Dim amount As FormattableString = $"The amount is {value:C}" 
            Console.WriteLine(amount.ToString())
            Console.WriteLine(amount.ToString(new CultureInfo("fr-FR")))
            Console.WriteLine(FormattableString.Invariant(amount))
        End Sub
    End Module
    ' The example displays the following output:
    '    The amount is $126.03
    '    The amount is 126,03 €
    '    The amount is ¤126.03
    

您可以保留非字符串数据作为二进制数据或作为格式化数据。You can persist non-string data either as binary data or as formatted data. 如果您选择将其保存为格式化数据,您应调用包括 provider 参数的格式设置方法重载,并向其传递 CultureInfo.InvariantCulture 属性。If you choose to save it as formatted data, you should call a formatting method overload that includes a provider parameter and pass it the CultureInfo.InvariantCulture property. 固定区域性为独立于区域性和计算机的格式化数据提供一致的格式。The invariant culture provides a consistent format for formatted data that is independent of culture and machine. 相反,使用区域性而非固定区域性进行格式化的持久性数据具有许多限制:In contrast, persisting data that is formatted by using cultures other than the invariant culture has a number of limitations:

  • 如果在具有不同区域性的系统上检索数据,或者如果当前系统用户更改当前区域性或者尝试检索数据时,该数据可能不可用。The data is likely to be unusable if it is retrieved on a system that has a different culture, or if the user of the current system changes the current culture and tries to retrieve the data.
  • 特定计算机上的区域性属性可能与标准值不同。The properties of a culture on a specific computer can differ from standard values. 任何时候,用户都可以自定义区分区域性的显示设置。At any time, a user can customize culture-sensitive display settings. 因此,在系统保存的格式化数据在用户自定义区域性设置之后可能无法读取。Because of this, formatted data that is saved on a system may not be readable after the user customizes cultural settings. 格式化数据在计算机之间移植可能会受到更多的限制。The portability of formatted data across computers is likely to be even more limited.
  • 管理数值或日期时间格式的国际、区域或国家标准会随着时间发生更改,这些更改会合并到 Windows 操作系统更新中。International, regional, or national standards that govern the formatting of numbers or dates and times change over time, and these changes are incorporated into Windows operating system updates. 在格式设置约定更改时,将无法读取使用以前的约定格式化的数据。When formatting conventions change, data that was formatted by using the previous conventions may become unreadable.

下面的示例演示了使用区分区域性格式设置进行持久化数据导致的有限可移植性。The following example illustrates the limited portability that results from using culture-sensitive formatting to persist data. 该示例将日期和时间数组值保存到文件中。The example saves an array of date and time values to a file. 这些数据通过使用英语(美国)区域性约定进行格式化。These are formatted by using the conventions of the English (United States) culture. 在应用程序将当前线程区域性更改为法语(瑞士)后,它尝试使用当前区域性的格式设置约定来读取保存的值。After the application changes the current thread culture to French (Switzerland), it tries to read the saved values by using the formatting conventions of the current culture. 尝试读取两个数据条目时引发 FormatException 异常,现在日期数组包含相当于 MinValue的两个错误元素。The attempt to read two of the data items throws a FormatException exception, and the array of dates now contains two incorrect elements that are equal to 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
Imports System.Globalization
Imports System.IO
Imports System.Text
Imports System.Threading

Module Example
   Private filename As String = ".\dates.dat"
   
   Public Sub Main()
      Dim dates() As Date = { #5/6/1758 9:26PM#, #5/5/1818 7:19AM#, _ 
                              #4/22/1870 11:54PM#, #9/8/1890 6:47AM#, _ 
                              #2/18/1905 3:12PM# }
      ' 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.
      Dim newDates() As Date = ReadData()
      For Each newDate In newDates
         Console.WriteLine(newDate.ToString("g"))
      Next
   End Sub
   
   Private Sub WriteData(dates() As Date)
      Dim sw As New StreamWriter(filename, False, Encoding.Utf8)    
      For ctr As Integer = 0 To dates.Length - 1
         sw.Write("{0}", dates(ctr).ToString("g", CultureInfo.CurrentCulture))
         If ctr < dates.Length - 1 Then sw.Write("|")   
      Next      
      sw.Close()
   End Sub
   
   Private Function ReadData() As Date()
      Dim exceptionOccurred As Boolean = False
           
      ' Read file contents as a single string, then split it.
      Dim sr As New StreamReader(filename, Encoding.Utf8)
      Dim output As String = sr.ReadToEnd()
      sr.Close()   

      Dim values() As String = output.Split( {"|"c } )
      Dim newDates(values.Length - 1) As Date 
      For ctr As Integer = 0 To values.Length - 1
         Try
            newDates(ctr) = DateTime.Parse(values(ctr), CultureInfo.CurrentCulture)
         Catch e As FormatException
            Console.WriteLine("Failed to parse {0}", values(ctr))
            exceptionOccurred = True
         End Try
      Next      
      If exceptionOccurred Then Console.WriteLine()
      Return newDates
   End Function
End Module
' 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.CurrentCulture 属性替换为 CultureInfo.InvariantCulture,则会成功还原持久的日期和时间数据,如以下输出所示:However, if you replace the CultureInfo.CurrentCulture property with CultureInfo.InvariantCulture in the calls to DateTime.ToString(String, IFormatProvider) and DateTime.Parse(String, IFormatProvider), the persisted date and time data is successfully restored, as the following output shows:

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

请参阅See also