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

在开发本地化和全球化应用程序方面,.NET Framework 可为开发人员提供全方位支持,通过它,可以在执行常见操作(如排序和显示字符串)时轻松地应用当前区域性或特定区域性的约定。 但是,排序或字符串比较并非总是区分区域性的操作。 例如,对于应用程序在内部使用的字符串,通常应该在所有区域性中以相同的方式对其进行处理。 如果将与区域性无关的字符串数据(例如,XML 标记、HTML 标记、用户名、文件路径以及系统对象的名称)解释为与区域性相关,则应用程序代码可能会遭遇细微错误、性能较差等问题,在一些情况下,还可能会遭遇安全问题。

此主题将分析 .NET Framework 4 版以及更高版本中的字符串排序、比较和大小写方法,针对如何选择适当的字符串处理方法提出建议,并提供有关字符串处理方法的其他信息。

本主题包含以下各节:

  • 推荐的字符串使用方法

  • 显式指定字符串比较

  • 字符串比较的详细信息

  • 选择方法调用的 StringComparison 成员

  • .NET Framework 中的常见字符串比较方法

  • 间接执行字符串比较的方法

推荐的字符串使用方法

使用 .NET Framework 进行开发时,请在使用字符串时遵循以下简单的建议:

在使用字符串时,请避免采用以下做法:

  • 请勿使用未显式或隐式为字符串操作指定字符串比较规则的重载。

  • 大多数情况下,请勿使用基于 StringComparison.InvariantCulture 的字符串操作。 有几种例外,其中一个是当您保存在语言方面有意义但区域性不明确的数据时。

  • 请勿使用 String.CompareCompareTo 方法的重载并测试返回的零值以确定两个字符串是否相等。

返回页首

显式指定字符串比较

重载 .NET Framework 中的大多数字符串操作方法。 通常情况下,一个或多个重载接受默认设置,而其他重载则不接受默认设置,而是定义比较或操作字符串的精确方法。 大多数不依赖于默认设置的方法都包括 StringComparison 类型的参数,该参数是按区域性和大小写显式指定字符串比较规则的枚举。 下表描述了 StringComparison 枚举成员。

StringComparison 成员

说明

CurrentCulture

使用当前区域性执行区分大小写的比较。

CurrentCultureIgnoreCase

使用当前区域性执行不区分大小写的比较。

InvariantCulture

使用固定区域性执行区分大小写的比较。

InvariantCultureIgnoreCase

使用固定区域性执行不区分大小写的比较。

Ordinal

执行序号比较。

OrdinalIgnoreCase

执行不区分大小写的序号比较。

例如,IndexOf 方法(它返回 String 对象中与某字符或字符串匹配的子字符串的索引)具有九种重载:

由于下列原因,建议您选择不使用默认值的重载:

  • 有些使用默认参数的重载(在字符串实例中搜索 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 参数的重载以明确方法调用的目的。

如果从语言的角度解释非语言字符串数据,或者使用其他区域性的约定解释某特定区域性中的字符串数据,则会发生或大或小的错误。 土耳其语 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 字符的字符串相等。

重要说明重要事项

虽然字符串比较方法忽略嵌入式 null 字符串,但字符串搜索方法(如 String.ContainsString.EndsWithString.IndexOfString.LastIndexOfString.StartsWith)不忽略嵌入式 null 字符串。

以下示例对字符串“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 属性。 将忽略这些已转换的字符间的全部大小写差异。

对于 ASCII 字符串而言,使用 StringComparison.InvariantCultureStringComparison.Ordinal 的比较的工作方式是相同的。 但是,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

value

区分大小写的内部标识符。

标准(如 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 构造函数

哈希字符串再次提供了受字符串比较方式影响的操作的示例。

以下示例通过以下方法将 Hashtable 对象实例化:向该对象传递由 StringComparer.OrdinalIgnoreCase 属性返回的 StringComparer 对象。 因为从 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);
   }
}

返回页首