您现在的位置是:网站首页> 编程资料编程资料

.NET中字符串比较的最佳用法_实用技巧_

2023-05-24 339人已围观

简介 .NET中字符串比较的最佳用法_实用技巧_

.NET 为开发本地化和全球化应用程序提供广泛支持,在执行排序和显示字符串等常见操作时,轻松应用当前区域性或特定区域性的约定。 但排序或比较字符串并不总是区分区域性的操作。

例如,对于应用程序内部使用的字符串,通常应该跨所有区域性以相同的方式对其进行处理。 如果将 XML 标记、HTML 标记、用户名、文件路径和系统对象名称等与区域性无关的字符串数据解释为区分区域性,则应用程序代码会遭遇细微的错误、不佳的性能,在某些情况下,还会遭遇安全性问题。

本文介绍 .NET 中的字符串排序、比较和大小写方法,针对如何选择适当的字符串处理方法提出建议,并提供有关字符串处理方法的其他信息。

对字符串用法的建议

使用 .NET 进行开发时,请遵循以下简要建议比较字符串:

  • 使用为字符串操作显式指定字符串比较规则的重载。 通常情况下,这涉及调用具有 StringComparison类型的参数的方法重载。
  • 使用 StringComparison.Ordinal 或 StringComparison.OrdinalIgnoreCase 进行比较,并以此作为匹配区域性不明确的字符串的安全默认设置。
  • 将比较与 StringComparison.Ordinal 或 StringComparison.OrdinalIgnoreCase 配合使用,以获得更好的性能。
  • 向用户显示输出时,使用基于 StringComparison.CurrentCulture 的字符串操作。
  • 当进行与语言(例如,符号)无关的比较时,使用非语言的 StringComparison.Ordinal 或 StringComparison.OrdinalIgnoreCase 值,而不使用基于 CultureInfo.InvariantCulture 的字符串操作。
  • 在规范化要比较的字符串时,使用 String.ToUpperInvariant 方法而非 String.ToLowerInvariant 方法。
  • 使用 String.Equals 方法的重载来测试两个字符串是否相等。
  • 使用 String.Compare 和 String.CompareTo 方法可对字符串进行排序,而不是检查字符串是否相等。
  • 在用户界面,使用区分区域性的格式显示非字符串数据,如数字和日期。 使用格式以固定区域性使非字符串数据显示为字符串形式。

比较字符串时,请避免采用以下做法:

  • 不要使用未显式或隐式为字符串操作指定字符串比较规则的重载。
  • 在大多数情况下,不要使用基于 StringComparison.InvariantCulture 的字符串操作。 其中的一个少数例外情况是,保存在语言上有意义但区域性不明确的数据。
  • 不要使用 String.Compare 或 CompareTo 方法的重载和用于确定两个字符串是否相等的返回值为 0 的测试。

显式指定字符串比较

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

StringComparison 成员描述
CurrentCulture使用当前区域性执行区分大小写的比较。
CurrentCultureIgnoreCase使用当前区域性执行不区分大小写的比较。
InvariantCulture使用固定区域性执行区分大小写的比较。
InvariantCultureIgnoreCase使用固定区域性执行不区分大小写的比较。
Ordinal执行序号比较。
OrdinalIgnoreCase执行不区分大小写的序号比较。

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

  • 默认情况下,IndexOf(Char), IndexOf(Char, Int32)和 IndexOf(Char, Int32, Int32)对字符串中的字符执行序号(区分大小写但不区分区域性的)搜索。
  • 默认情况下,IndexOf(String), IndexOf(String, Int32)和 IndexOf(String, Int32, Int32)对字符串中的子字符串执行区分大小写且区分区域性的搜索。
  • IndexOf(String, StringComparison)、 IndexOf(String, Int32, StringComparison)和 IndexOf(String, Int32, Int32, StringComparison),其中包括 StringComparison 类型的参数,该类型允许指定比较形式。

我们建议选择不使用默认值的重载,原因如下:

  • 具有默认参数的一些重载(在字符串实例中搜索 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”。 此外,比较可隐式确定相等性。 对于认为是相等的字符串,比较操作将返回零。 对此很好的解释是两个字符串都不小于对方。 涉及到字符串的最有意义的操作包括这些步骤中的一个或两个步骤:与另一个字符串进行比较和执行明确的排序操作。

[!NOTE]

可以下载排序权重表,这是一组文本文件,其中包含有关 Windows 操作系统排序和比较操作中所使用的字符权重的信息,也可以下载默认 Unicode 排序元素表,这是适用于 Linux 和 macOS 的最新版排序权重表。 Linux 和 macOS 上的特定排序权重表版本取决于系统上安装的 International Components for Unicode 库的版本。 有关 ICU 版本及它们所实现的 Unicode 版本的信息,请参阅下载 ICU

但是,评估两个字符串的相等性或排序顺序不会生成一个正确的结果;其结果取决于用于比较这两个字符串的条件。 特别是,序号或基于当前区域性或固定区域性(基于英语语言的区域设置不明确的区域性)的大小写和排序约定的字符串比较可能会产生不同的结果。

此外,使用不同 .NET 版本或在不同操作系统或不同的操作系统版本上使用 .NET 进行字符串比较时,返回的结果可能不同。 有关详细信息,请参阅字符串和 Unicode 标准。

使用当前区域性的字符串比较

一个条件涉及在比较字符串时使用当前区域性的约定。 基于当前区域性的比较使用线程的当前区域性或区域设置。 如果用户未设置该区域性,则默认为“控制面板”中“区域选项” 窗口中的设置。 当数据与语言相关并反映区分区域性的用户交互时,应始终使用基于当前区域性的比较。

但是,当区域性发生更改时,.NET 中的比较和大小写行为也发生更改。 如果执行应用程序的计算机与用于开发该应用程序的计算机具有不同的区域性,或者执行线程改变它的区域性,则会发生这种情况。 此行为是有意而为之的,但许多开发人员不易察觉此行为。 下面的示例说明了美国英语(“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

使用当前区域性的不区分大小写比较和区分区域性的比较是相同的,只不过前者忽略由线程的当前区域性指示的大小写。 这种情况也可表明它的排序顺序。

以下方法默认利用使用当前区域性语义的比较:

  • 不包括String.Compare 参数的 StringComparison 重载。
  • String.CompareTo 重载。
  • 默认 String.StartsWith(String) 方法和具有 String.StartsWith(String, Boolean, CultureInfo) null nullCultureInfo 重载。
  • 默认 String.EndsWith(String) 方法和需要使用 nullCultureInfo 参数的 String.EndsWith(String, Boolean, CultureInfo) 方法。
  • 接受String.IndexOf 作为搜索参数且不包含 String 参数的 StringComparison 重载。
  • 接受String.LastIndexOf 作为搜索参数且不包含 String 参数的 StringComparison 重载。

总之,我们建议调用具有 参数的重载,以便明确方法调用的意图。

当从语言角度解释非语言的字符串数据,或利用其他区域性的约定解释某个特定区域性中的字符串时,则会发生或大或小的错误。 土耳其语 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:”开头的不区分大小写的安全措施。

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

序号字符串操作

在方法调用中指定 StringComparison.Ordinal 或

-六神源码网