System.String 类

本文提供了此 API 参考文档的补充说明。

字符串是用于表示文本的字符的顺序集合。 对象 String 是表示字符串的对象的顺序集合 System.Char ;对象 System.Char 对应于 UTF-16 代码单元。 对象的值 String 是对象的顺序集合 System.Char 的内容,该值是不可变的(即,它是只读的)。 有关字符串不可变性的详细信息,请参阅 Immutability 和 StringBuilder 类 部分。 内存中对象的最大大小 String 为 2-GB 或大约 10 亿个字符。

有关 Unicode、UTF-16、代码单元、码位和CharRune类型的详细信息,请参阅 .NET 中的字符编码简介。

实例化 String 对象

可以通过以下方式实例化 String 对象:

  • 将字符串字面值赋予 String 变量。 这是用于创建字符串的最常用方法。 以下示例使用赋值创建多个字符串。 请注意,在 C# 和 F# 中,由于反斜杠 (\) 是一个转义符,字符串中的字面反斜杠必须进行转义,或者整个字符串必须使用 @ 引起来。

    string string1 = "This is a string created by assignment.";
    Console.WriteLine(string1);
    string string2a = "The path is C:\\PublicDocuments\\Report1.doc";
    Console.WriteLine(string2a);
    string string2b = @"The path is C:\PublicDocuments\Report1.doc";
    Console.WriteLine(string2b);
    // The example displays the following output:
    //       This is a string created by assignment.
    //       The path is C:\PublicDocuments\Report1.doc
    //       The path is C:\PublicDocuments\Report1.doc
    
    let string1 = "This is a string created by assignment."
    printfn "%s" string1
    let string2a = "The path is C:\\PublicDocuments\\Report1.doc"
    printfn "%s" string2a
    let string2b = @"The path is C:\PublicDocuments\Report1.doc"
    printfn "%s" string2b
    // The example displays the following output:
    //       This is a string created by assignment.
    //       The path is C:\PublicDocuments\Report1.doc
    //       The path is C:\PublicDocuments\Report1.doc
    
    Dim string1 As String = "This is a string created by assignment."
    Console.WriteLine(string1)
    Dim string2 As String = "The path is C:\PublicDocuments\Report1.doc"
    Console.WriteLine(string2)
    ' The example displays the following output:
    '       This is a string created by assignment.
    '       The path is C:\PublicDocuments\Report1.doc
    
  • 通过调用 String 类构造函数。 以下示例通过调用多个类构造函数来实例化字符串。 请注意,某些构造函数包括指向字符数组或带符号字节数组的指针作为参数。 Visual Basic 不支持对这些构造函数的调用。 有关 String 构造函数的详细信息,请参阅 String 构造函数摘要。

    char[] chars = { 'w', 'o', 'r', 'd' };
    sbyte[] bytes = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x00 };
    
    // Create a string from a character array.
    string string1 = new string(chars);
    Console.WriteLine(string1);
    
    // Create a string that consists of a character repeated 20 times.
    string string2 = new string('c', 20);
    Console.WriteLine(string2);
    
    string stringFromBytes = null;
    string stringFromChars = null;
    unsafe
    {
       fixed (sbyte* pbytes = bytes)
       {
          // Create a string from a pointer to a signed byte array.
          stringFromBytes = new string(pbytes);
       }
       fixed (char* pchars = chars)
       {
          // Create a string from a pointer to a character array.
          stringFromChars = new string(pchars);
       }
    }
    Console.WriteLine(stringFromBytes);
    Console.WriteLine(stringFromChars);
    // The example displays the following output:
    //       word
    //       cccccccccccccccccccc
    //       ABCDE
    //       word
    
    let chars = [| 'w'; 'o'; 'r'; 'd' |]
    let bytes = [| 0x41y; 0x42y; 0x43y; 0x44y; 0x45y; 0x00y |]
    
    // Create a string from a character array.
    let string1 = String chars
    printfn "%s" string1
    
    // Create a string that consists of a character repeated 20 times.
    let string2 = String('c', 20)
    printfn "%s" string2
    
    let stringFromBytes =
        // Create a string from a pointer to a signed byte array.
        use pbytes = fixed bytes
        String pbytes
    let stringFromChars = 
        // Create a string from a pointer to a character array.
        use pchars = fixed chars
        String pchars
    
    printfn $"{stringFromBytes}"
    printfn $"{stringFromChars}"
    // The example displays the following output:
    //       word
    //       cccccccccccccccccccc
    //       ABCDE
    //       word
    
    Dim chars() As Char = {"w"c, "o"c, "r"c, "d"c}
    
    ' Create a string from a character array.
    Dim string1 As New String(chars)
    Console.WriteLine(string1)
    
    ' Create a string that consists of a character repeated 20 times.
    Dim string2 As New String("c"c, 20)
    Console.WriteLine(string2)
    ' The example displays the following output:
    '       word
    '       cccccccccccccccccccc
    
  • 通过使用字符串连接运算符(在 C# 和 F# 中为 +,在 Visual Basic 中为 & 或 +),可以从任意组合的 实例和字符串文本中创建一个单一字符串。 下面的示例演示了字符串串联运算符的使用。

    string string1 = "Today is " + DateTime.Now.ToString("D") + ".";
    Console.WriteLine(string1);
    
    string string2 = "This is one sentence. " + "This is a second. ";
    string2 += "This is a third sentence.";
    Console.WriteLine(string2);
    // The example displays output like the following:
    //    Today is Tuesday, July 06, 2011.
    //    This is one sentence. This is a second. This is a third sentence.
    
    let string1 = "Today is " + DateTime.Now.ToString("D") + "."
    printfn $"{string1}"
    
    let string2 = "This is one sentence. " + "This is a second. "
    let string2 = string2 + "This is a third sentence."
    printfn $"{string2}"
    // The example displays output like the following:
    //    Today is Tuesday, July 06, 2011.
    //    This is one sentence. This is a second. This is a third sentence.
    
    Dim string1 As String = "Today is " + Date.Now.ToString("D") + "."
    Console.WriteLine(string1)
    Dim string2 As String = "This is one sentence. " + "This is a second. "
    string2 += "This is a third sentence."
    Console.WriteLine(string2)
    ' The example displays output like the following:
    '    Today is Tuesday, July 06, 2011.
    '    This is one sentence. This is a second. This is a third sentence.
    
  • 通过检索属性或调用返回字符串的方法。 下面的示例使用类的方法 String 从较大的字符串中提取子字符串。

    string sentence = "This sentence has five words.";
    // Extract the second word.
    int startPosition = sentence.IndexOf(" ") + 1;
    string word2 = sentence.Substring(startPosition,
                                      sentence.IndexOf(" ", startPosition) - startPosition);
    Console.WriteLine("Second word: " + word2);
    // The example displays the following output:
    //       Second word: sentence
    
    let sentence = "This sentence has five words."
    // Extract the second word.
    let startPosition = sentence.IndexOf " " + 1
    let word2 = 
        sentence.Substring(startPosition, sentence.IndexOf(" ", startPosition) - startPosition)
    printfn $"Second word: {word2}"
    // The example displays the following output:
    //       Second word: sentence
    
    Dim sentence As String = "This sentence has five words."
    ' Extract the second word.
    Dim startPosition As Integer = sentence.IndexOf(" ") + 1
    Dim word2 As String = sentence.Substring(startPosition,
                                           sentence.IndexOf(" ", startPosition) - startPosition)
    Console.WriteLine("Second word: " + word2)
    ' The example displays the following output:
    '       Second word: sentence
    
  • 通过调用格式设置方法将值或对象转换为其字符串表示形式。 以下示例使用 复合格式设置 功能将两个对象的字符串表示形式嵌入字符串。

    DateTime dateAndTime = new DateTime(2011, 7, 6, 7, 32, 0);
    double temperature = 68.3;
    string result = String.Format("At {0:t} on {0:D}, the temperature was {1:F1} degrees Fahrenheit.",
                                  dateAndTime, temperature);
    Console.WriteLine(result);
    // The example displays the following output:
    //       At 7:32 AM on Wednesday, July 06, 2011, the temperature was 68.3 degrees Fahrenheit.
    
    let dateAndTime = DateTime(2011, 7, 6, 7, 32, 0)
    let temperature = 68.3
    String.Format("At {0:t} on {0:D}, the temperature was {1:F1} degrees Fahrenheit.", dateAndTime, temperature)
    |> printfn "%s"
    // The example displays the following output:
    //       At 7:32 AM on Wednesday, July 06, 2011, the temperature was 68.3 degrees Fahrenheit.
    
    Dim dateAndTime As DateTime = #07/06/2011 7:32:00AM#
    Dim temperature As Double = 68.3
    Dim result As String = String.Format("At {0:t} on {0:D}, the temperature was {1:F1} degrees Fahrenheit.",
                                       dateAndTime, temperature)
    Console.WriteLine(result)
    ' The example displays the following output:
    '       At 7:32 AM on Wednesday, July 06, 2011, the temperature was 68.3 degrees Fahrenheit.
    

Char 对象和 Unicode 字符

字符串中的每个字符都由 Unicode 标量值定义,也称为 Unicode 代码点或 Unicode 字符的序号(数字)值。 每个代码点都使用 UTF-16 编码进行编码,编码的每个元素的数值由对象 Char 表示。

注意

请注意,由于 String 实例是由一系列 UTF-16 代码单元组成,因此可能创建的 String 对象并不是一个格式正确的 Unicode 字符串。 例如,可以创建具有低代理项且没有相应高代理项的字符串。 尽管某些方法(如命名空间中的 System.Text 编码和解码对象的方法)可能会执行检查以确保字符串格式正确, String 但类成员不确保字符串格式正确。

单个 Char 对象通常表示单个编码点,即 Char 的数值等于编码点。 例如,字符“a”的代码点为 U+0061。 但是,代码点可能需要多个编码元素(多个 Char 对象)。 Unicode 标准定义了两种类型的字符,这些字符对应于多个 Char 对象:图形和 Unicode 补充代码点,这些代码点对应于 Unicode 补充平面中的字符。

  • 图形由基字符表示,后跟一个或多个组合字符。 例如,字符 ä 是通过由代码点为 U+0061 的 Char 对象和代码点为 U+0308 的 Char 对象组合表示的。 此字符也可以由具有 U+00E4 代码点的单个 Char 对象定义。 如以下示例所示,区分区域性的相等比较表明这两种表示形式是相等的,尽管普通的按顺序比较不会认为它们相等。 但是,如果规范化了两个字符串,则序号比较也表示它们相等。 (有关规范化字符串的详细信息,请参阅 规范化 部分。)

    using System;
    using System.Globalization;
    using System.IO;
    
    public class Example5
    {
       public static void Main()
       {
          StreamWriter sw = new StreamWriter(@".\graphemes.txt");
          string grapheme = "\u0061\u0308";
          sw.WriteLine(grapheme);
          
          string singleChar = "\u00e4";
          sw.WriteLine(singleChar);
                
          sw.WriteLine("{0} = {1} (Culture-sensitive): {2}", grapheme, singleChar, 
                       String.Equals(grapheme, singleChar, 
                                     StringComparison.CurrentCulture));
          sw.WriteLine("{0} = {1} (Ordinal): {2}", grapheme, singleChar, 
                       String.Equals(grapheme, singleChar, 
                                     StringComparison.Ordinal));
          sw.WriteLine("{0} = {1} (Normalized Ordinal): {2}", grapheme, singleChar, 
                       String.Equals(grapheme.Normalize(), 
                                     singleChar.Normalize(), 
                                     StringComparison.Ordinal));
          sw.Close(); 
       }
    }
    // The example produces the following output:
    //       ä
    //       ä
    //       ä = ä (Culture-sensitive): True
    //       ä = ä (Ordinal): False
    //       ä = ä (Normalized Ordinal): True
    
    open System
    open System.IO
    
    do
        use sw = new StreamWriter(@".\graphemes.txt")
        let grapheme = "\u0061\u0308"
        sw.WriteLine grapheme
    
        let singleChar = "\u00e4"
        sw.WriteLine singleChar
    
        sw.WriteLine("{0} = {1} (Culture-sensitive): {2}", grapheme, singleChar, 
                    String.Equals(grapheme, singleChar,
                                    StringComparison.CurrentCulture))
        sw.WriteLine("{0} = {1} (Ordinal): {2}", grapheme, singleChar,
                    String.Equals(grapheme, singleChar,
                                    StringComparison.Ordinal))
        sw.WriteLine("{0} = {1} (Normalized Ordinal): {2}", grapheme, singleChar,
                    String.Equals(grapheme.Normalize(),
                                    singleChar.Normalize(),
                                    StringComparison.Ordinal))
    // The example produces the following output:
    //       ä
    //       ä
    //       ä = ä (Culture-sensitive): True
    //       ä = ä (Ordinal): False
    //       ä = ä (Normalized Ordinal): True
    
    Imports System.Globalization
    Imports System.IO
    
    Module Example9
        Public Sub Main()
            Dim sw As New StreamWriter(".\graphemes.txt")
            Dim grapheme As String = ChrW(&H61) + ChrW(&H308)
            sw.WriteLine(grapheme)
    
            Dim singleChar As String = ChrW(&HE4)
            sw.WriteLine(singleChar)
    
            sw.WriteLine("{0} = {1} (Culture-sensitive): {2}", grapheme, singleChar,
                       String.Equals(grapheme, singleChar,
                                     StringComparison.CurrentCulture))
            sw.WriteLine("{0} = {1} (Ordinal): {2}", grapheme, singleChar,
                       String.Equals(grapheme, singleChar,
                                     StringComparison.Ordinal))
            sw.WriteLine("{0} = {1} (Normalized Ordinal): {2}", grapheme, singleChar,
                       String.Equals(grapheme.Normalize(),
                                     singleChar.Normalize(),
                                     StringComparison.Ordinal))
            sw.Close()
        End Sub
    End Module
    ' The example produces the following output:
    '       ä
    '       ä
    '       ä = ä (Culture-sensitive): True
    '       ä = ä (Ordinal): False
    '       ä = ä (Normalized Ordinal): True
    
  • 一个 Unicode 补充代码点(代理项对)由一个代码点为高代理项的 Char 对象表示,其后跟着一个代码点为低代理项的 Char 对象。 高代理项的代码单元范围是从 U+D800 到 U+DBFF。 低代理项的代码单元范围是从 U+DC00 到 U+DFFF。 代理项对用于表示 16 个 Unicode 补充平面中的字符。 以下示例创建代理字符并将其传递给 Char.IsSurrogatePair(Char, Char) 方法,以确定其是否为代理对。

    string surrogate = "\uD800\uDC03";
    for (int ctr = 0; ctr < surrogate.Length; ctr++) 
       Console.Write($"U+{(ushort)surrogate[ctr]:X2} ");
    
    Console.WriteLine();
    Console.WriteLine($"   Is Surrogate Pair: {Char.IsSurrogatePair(surrogate[0], surrogate[1])}");
    // The example displays the following output:
    //       U+D800 U+DC03
    //          Is Surrogate Pair: True
    
    open System
    
    let surrogate = "\uD800\uDC03"
    for i = 0 to surrogate.Length - 1 do
        printf $"U+{uint16 surrogate[i]:X2} "
    
    printfn $"\n   Is Surrogate Pair: {Char.IsSurrogatePair(surrogate[0], surrogate[1])}"
    // The example displays the following output:
    //       U+D800 U+DC03
    //          Is Surrogate Pair: True
    
    Module Example20
        Public Sub Main()
            Dim surrogate As String = ChrW(&HD800) + ChrW(&HDC03)
            For ctr As Integer = 0 To surrogate.Length - 1
                Console.Write("U+{0:X2} ", Convert.ToUInt16(surrogate(ctr)))
            Next
            Console.WriteLine()
            Console.WriteLine("   Is Surrogate Pair: {0}",
                            Char.IsSurrogatePair(surrogate(0), surrogate(1)))
        End Sub
    End Module
    
    ' The example displays the following output:
    '       U+D800 U+DC03
    '          Is Surrogate Pair: True
    

Unicode 标准

字符串中的字符由 UTF-16 编码的代码单元表示,它们对应于 Char 值。

字符串中的每个字符都有关联的 Unicode 字符类别,该类别由 UnicodeCategory 枚举在 .NET 中表示。 可以通过调用 CharUnicodeInfo.GetUnicodeCategory 方法来确定字符或代理项对的类别。

.NET 维护自己的字符表及其相应的类别,这可确保在不同平台上运行的特定版本的 .NET 实现返回相同的字符类别信息。 在所有 .NET 版本和所有 OS 平台上,字符类别信息由 Unicode 字符数据库提供。

下表列出了 .NET 版本及其字符类别所基于的 Unicode 标准。

.NET 版本 Unicode 标准版本
.NET Framework 1.1 Unicode 标准,版本 4.0.0
.NET Framework 2.0 Unicode 标准,版本 5.0.0
.NET Framework 3.5 Unicode 标准,版本 5.0.0
.NET Framework 4 Unicode 标准,版本 5.0.0
.NET Framework 4.5 Unicode 标准,版本 6.3.0
.NET Framework 4.5.1 Unicode 标准,版本 6.3.0
.NET Framework 4.5.2 Unicode 标准,版本 6.3.0
.NET Framework 4.6 Unicode 标准,版本 6.3.0
.NET Framework 4.6.1 Unicode 标准,版本 6.3.0
.NET Framework 4.6.2 及更高版本 Unicode 标准,版本 8.0.0
.NET Core 2.1 Unicode 标准,版本 8.0.0
.NET Core 3.1 Unicode 标准版本 11.0.0
.NET 5 Unicode 标准版本 13.0.0

此外,.NET 还支持基于 Unicode 标准进行字符串比较和排序。 从在 Windows 8 及更高版本的 Windows 操作系统上运行的 .NET Framework 4.5 开始,运行时会将字符串比较和排序操作委托给操作系统。 在 .NET Core 和 .NET 5+上,字符串比较和排序信息由 Unicode 库的国际组件提供(Windows 10 2019 年 5 月更新 之前的 Windows 版本除外)。 下表列出了 .NET 的版本和 Unicode 标准版本,以及字符比较和排序所基于的版本。

.NET 版本 Unicode 标准版本
Windows 7 上的 .NET Framework 4.5 及更高版本 Unicode 标准,版本 5.0.0
Windows 8 及更高版本的 Windows 操作系统上的 .NET Framework 4.5 及更高版本 Unicode 标准,版本 6.3.0
.NET Core 和 .NET 5+ 取决于基础操作系统支持的 Unicode 标准版本。

嵌入的空字符

在 .NET 中,对象 String 可以包含嵌入的 null 字符,这些字符算作字符串长度的一部分。 但是,在某些语言(如 C 和 C++)中,null 字符指示字符串的末尾;它不被视为字符串的一部分,并且不算作字符串长度的一部分。 这意味着,C 和 C++ 程序员或使用 C 或 C++ 编写的库关于字符串所做出的以下常见假设,在应用于 String 对象时不一定有效:

  • strlenwcslen函数返回的值不一定相等String.Length

  • strcpy_swcscpy_s 函数创建的字符串不一定与所复制的字符串相同。

应确保本机 C 和C++实例化 String 对象的代码,以及通过平台调用传递 String 对象的代码,不要假定嵌入的 null 字符标记字符串的末尾。

在对字符串进行排序(或比较)和搜索字符串时,字符串中的嵌入 null 字符也会受到不同的处理。 在对两个字符串执行区分区域性的比较(包括使用固定区域性进行的比较)时,空字符会被忽略。 只有在进行按顺序比较或不区分大小写的按顺序比较时,才会考虑空字符。 另一方面,在使用ContainsStartsWithIndexOf等方法搜索字符串时,始终会处理嵌入的 null 字符。

字符串和索引

索引是一个Char对象(而不是 Unicode 字符)在String中的位置。 索引是从字符串中第一个位置开始的从零开始的非负数,即索引位置为零。 许多搜索方法(例如 IndexOf ,以及 LastIndexOf)在字符串实例中返回字符或子字符串的索引。

Chars[] 属性允许你通过字符串中的索引位置访问各个 Char 对象。 由于该 Chars[] 属性是默认属性(在 Visual Basic 中)或索引器(在 C# 和 F# 中),因此可以使用如下代码访问字符串中的单个 Char 对象。 此代码查找字符串中的空格或标点符号字符,以确定字符串包含的单词数。

string s1 = "This string consists of a single short sentence.";
int nWords = 0;

s1 = s1.Trim();      
for (int ctr = 0; ctr < s1.Length; ctr++) {
   if (Char.IsPunctuation(s1[ctr]) | Char.IsWhiteSpace(s1[ctr]))
      nWords++;              
}
Console.WriteLine($"""
 The sentence
   {s1}
 has {nWords} words.
 """);                                                                     
// The example displays the following output:
//       The sentence
//          This string consists of a single short sentence.
//       has 8 words.
let s1 = "This string consists of a single short sentence."
let mutable nWords = 0

for i = 0 to s1.Length - 1 do
    if Char.IsPunctuation s1[i] || Char.IsWhiteSpace s1[i] then
        nWords <- nWords + 1
printfn $"The sentence\n   {s1}\nhas {nWords} words."
// The example displays the following output:
//       The sentence
//          This string consists of a single short sentence.
//       has 8 words.
Module Example12
    Public Sub Main()
        Dim s1 As String = "This string consists of a single short sentence."
        Dim nWords As Integer = 0

        s1 = s1.Trim()
        For ctr As Integer = 0 To s1.Length - 1
            If Char.IsPunctuation(s1(ctr)) Or Char.IsWhiteSpace(s1(ctr)) Then
                nWords += 1
            End If
        Next
        Console.WriteLine("The sentence{2}   {0}{2}has {1} words.",
                        s1, nWords, vbCrLf)
    End Sub
End Module
' The example displays the following output:
'       The sentence
'          This string consists of a single short sentence.
'       has 8 words.

由于String类实现了IEnumerable接口,因此可以使用foreach结构迭代访问Char字符串中的对象,如以下示例所示。

string s1 = "This string consists of a single short sentence.";
int nWords = 0;

s1 = s1.Trim();      
foreach (var ch in s1) {
   if (Char.IsPunctuation(ch) | Char.IsWhiteSpace(ch))
      nWords++;              
}
Console.WriteLine($"""
 The sentence
   {s1}
 has {nWords} words.
 """);
// The example displays the following output:
//       The sentence
//          This string consists of a single short sentence.
//       has 8 words.
let s1 = "This string consists of a single short sentence."
let mutable nWords = 0

for ch in s1 do
    if Char.IsPunctuation ch || Char.IsWhiteSpace ch then
        nWords <- nWords + 1
printfn $"The sentence\n   {s1}\nhas {nWords} words."
// The example displays the following output:
//       The sentence
//          This string consists of a single short sentence.
//       has 8 words.
Module Example13
    Public Sub Main()
        Dim s1 As String = "This string consists of a single short sentence."
        Dim nWords As Integer = 0

        s1 = s1.Trim()
        For Each ch In s1
            If Char.IsPunctuation(ch) Or Char.IsWhiteSpace(ch) Then
                nWords += 1
            End If
        Next
        Console.WriteLine("The sentence{2}   {0}{2}has {1} words.",
                        s1, nWords, vbCrLf)
    End Sub
End Module
' The example displays the following output:
'       The sentence
'          This string consists of a single short sentence.
'       has 8 words.

连续索引值可能与连续 Unicode 字符不对应,因为 Unicode 字符可能编码为多个 Char 对象。 特别是,一个字符串可能包含由一个基字符后跟一个或多个组合字符所组成的多字符文本单元,或者包含由代理项对组成的多字符文本单元。 若要使用 Unicode 字符而不是 Char 对象,请使用 System.Globalization.StringInfoTextElementEnumerator 类或 String.EnumerateRunes 方法和 Rune 结构。 以下示例演示了与 Char 对象一起工作的代码与使用 Unicode 字符的代码之间的差异。 它比较句子的每个单词中的字符数或文本元素数。 该字符串包含两个由一个基字符后跟一个组合字符组成的序列。

// First sentence of The Mystery of the Yellow Room, by Leroux.
string opening = "Ce n'est pas sans une certaine émotion que "+
                 "je commence à raconter ici les aventures " +
                 "extraordinaires de Joseph Rouletabille."; 
// Character counters.
int nChars = 0;
// Objects to store word count.
List<int> chars = new List<int>();
List<int> elements = new List<int>();

foreach (var ch in opening) {
   // Skip the ' character.
   if (ch == '\u0027') continue;
        
   if (Char.IsWhiteSpace(ch) | (Char.IsPunctuation(ch))) {
      chars.Add(nChars);
      nChars = 0;
   }
   else {
      nChars++;
   }
}

System.Globalization.TextElementEnumerator te = 
   System.Globalization.StringInfo.GetTextElementEnumerator(opening);
while (te.MoveNext()) {
   string s = te.GetTextElement();   
   // Skip the ' character.
   if (s == "\u0027") continue;
   if ( String.IsNullOrEmpty(s.Trim()) | (s.Length == 1 && Char.IsPunctuation(Convert.ToChar(s)))) {
      elements.Add(nChars);         
      nChars = 0;
   }
   else {
      nChars++;
   }
}

// Display character counts.
Console.WriteLine("{0,6} {1,20} {2,20}",
                  "Word #", "Char Objects", "Characters"); 
for (int ctr = 0; ctr < chars.Count; ctr++) 
   Console.WriteLine("{0,6} {1,20} {2,20}",
                     ctr, chars[ctr], elements[ctr]); 
// The example displays the following output:
//       Word #         Char Objects           Characters
//            0                    2                    2
//            1                    4                    4
//            2                    3                    3
//            3                    4                    4
//            4                    3                    3
//            5                    8                    8
//            6                    8                    7
//            7                    3                    3
//            8                    2                    2
//            9                    8                    8
//           10                    2                    1
//           11                    8                    8
//           12                    3                    3
//           13                    3                    3
//           14                    9                    9
//           15                   15                   15
//           16                    2                    2
//           17                    6                    6
//           18                   12                   12
open System
open System.Globalization

// First sentence of The Mystery of the Yellow Room, by Leroux.
let opening = "Ce n'est pas sans une certaine émotion que je commence à raconter ici les aventures extraordinaires de Joseph Rouletabille."
// Character counters.
let mutable nChars = 0
// Objects to store word count.
let chars = ResizeArray<int>()
let elements = ResizeArray<int>()

for ch in opening do
    // Skip the ' character.
    if ch <> '\u0027' then
        if Char.IsWhiteSpace ch || Char.IsPunctuation ch then
            chars.Add nChars
            nChars <- 0
        else
            nChars <- nChars + 1

let te = StringInfo.GetTextElementEnumerator opening
while te.MoveNext() do
    let s = te.GetTextElement()
    // Skip the ' character.
    if s <> "\u0027" then
        if String.IsNullOrEmpty(s.Trim()) || (s.Length = 1 && Char.IsPunctuation(Convert.ToChar s)) then
            elements.Add nChars
            nChars <- 0
        else
            nChars <- nChars + 1

// Display character counts.
printfn "%6s %20s %20s" "Word #" "Char Objects " "Characters"
for i = 0 to chars.Count - 1 do
    printfn "%6d %20d %20d" i chars[i] elements[i]
// The example displays the following output:
//       Word #         Char Objects           Characters
//            0                    2                    2
//            1                    4                    4
//            2                    3                    3
//            3                    4                    4
//            4                    3                    3
//            5                    8                    8
//            6                    8                    7
//            7                    3                    3
//            8                    2                    2
//            9                    8                    8
//           10                    2                    1
//           11                    8                    8
//           12                    3                    3
//           13                    3                    3
//           14                    9                    9
//           15                   15                   15
//           16                    2                    2
//           17                    6                    6
//           18                   12                   12
Imports System.Collections.Generic
Imports System.Globalization

Module Example14
    Public Sub Main()
        ' First sentence of The Mystery of the Yellow Room, by Leroux.
        Dim opening As String = "Ce n'est pas sans une certaine émotion que " +
                              "je commence à raconter ici les aventures " +
                              "extraordinaires de Joseph Rouletabille."
        ' Character counters.
        Dim nChars As Integer = 0
        ' Objects to store word count.
        Dim chars As New List(Of Integer)()
        Dim elements As New List(Of Integer)()

        For Each ch In opening
            ' Skip the ' character.
            If ch = ChrW(&H27) Then Continue For

            If Char.IsWhiteSpace(ch) Or Char.IsPunctuation(ch) Then
                chars.Add(nChars)
                nChars = 0
            Else
                nChars += 1
            End If
        Next

        Dim te As TextElementEnumerator = StringInfo.GetTextElementEnumerator(opening)
        Do While te.MoveNext()
            Dim s As String = te.GetTextElement()
            ' Skip the ' character.
            If s = ChrW(&H27) Then Continue Do
            If String.IsNullOrEmpty(s.Trim()) Or (s.Length = 1 AndAlso Char.IsPunctuation(Convert.ToChar(s))) Then
                elements.Add(nChars)
                nChars = 0
            Else
                nChars += 1
            End If
        Loop

        ' Display character counts.
        Console.WriteLine("{0,6} {1,20} {2,20}",
                        "Word #", "Char Objects", "Characters")
        For ctr As Integer = 0 To chars.Count - 1
            Console.WriteLine("{0,6} {1,20} {2,20}",
                           ctr, chars(ctr), elements(ctr))
        Next
    End Sub
End Module
' The example displays the following output:
'    Word #         Char Objects           Characters
'         0                    2                    2
'         1                    4                    4
'         2                    3                    3
'         3                    4                    4
'         4                    3                    3
'         5                    8                    8
'         6                    8                    7
'         7                    3                    3
'         8                    2                    2
'         9                    8                    8
'        10                    2                    1
'        11                    8                    8
'        12                    3                    3
'        13                    3                    3
'        14                    9                    9
'        15                   15                   15
'        16                    2                    2
'        17                    6                    6
'        18                   12                   12

此示例使用 StringInfo.GetTextElementEnumerator 方法和 TextElementEnumerator 类来枚举字符串中的所有文本元素,从而处理文本元素。 还可以通过调用该方法检索包含每个文本元素的起始索引的 StringInfo.ParseCombiningCharacters 数组。

有关使用文本单位而不是单个 Char 值的详细信息,请参阅 .NET 中的字符编码简介。

Null 字符串和空字符串

已声明但尚未分配值的字符串为 null。 尝试在该字符串上调用方法会引发一个 NullReferenceException。 null字符串不同于空字符串,后者的值为“”或String.Empty。 在某些情况下,将 null 字符串或空字符串作为方法调用中的参数传递将引发异常。 例如,将 null 字符串传递给 Int32.Parse 方法将引发一个 ArgumentNullException,并传递空字符串将引发一个 FormatException。 在其他情况下,方法参数可以是 null 字符串或空字符串。 例如,如果要为类提供 IFormattable 实现,则希望将 null 字符串和空字符串等同于常规格式说明符(“G”)格式说明符。

String 类包括以下两种方便方法,可用于测试字符串 null 是还是空:

  • IsNullOrEmpty,用于指示字符串是 null 或等于 String.Empty。 此方法无需使用以下代码:

    if (str == null || str.Equals(String.Empty))
    
    if str = null || str.Equals String.Empty then
    
    If str Is Nothing OrElse str.Equals(String.Empty) Then
    
  • IsNullOrWhiteSpace,它指示字符串 null是、等于 String.Empty还是只包含空格字符。 此方法无需使用以下代码:

    if (str == null || str.Equals(String.Empty) || str.Trim().Equals(String.Empty))
    
    if str = null || str.Equals String.Empty || str.Trim().Equals String.Empty then
    
    If str Is Nothing OrElse str.Equals(String.Empty) OrElse str.Trim().Equals(String.Empty) Then
    

以下示例在实现自定义Temperature类时使用了IsNullOrEmpty方法。 该方法支持“G”、“C”、“F”和“K”格式字符串。 如果一个空格式字符串或一个其值为null的格式字符串被传递给方法,其值将更改为“G”格式字符串。

public string ToString(string format, IFormatProvider provider) 
{
   if (String.IsNullOrEmpty(format)) format = "G";  
   if (provider == null) provider = CultureInfo.CurrentCulture;
   
   switch (format.ToUpperInvariant())
   {
      // Return degrees in Celsius.    
      case "G":
      case "C":
         return temp.ToString("F2", provider) + "°C";
      // Return degrees in Fahrenheit.
      case "F": 
         return (temp * 9 / 5 + 32).ToString("F2", provider) + "°F";
      // Return degrees in Kelvin.
      case "K":   
         return (temp + 273.15).ToString();
      default:
         throw new FormatException(
               String.Format("The {0} format string is not supported.", 
                             format));
   }                                   
}
member _.ToString(format: string, provider: IFormatProvider) =
    let format = 
        if String.IsNullOrEmpty format then "G" else format
    
    let provider: IFormatProvider = 
        if provider = null then CultureInfo.CurrentCulture else provider

    match format.ToUpperInvariant() with
    // Return degrees in Celsius.
    | "G"
    | "C" ->
        temp.ToString("F2", provider) + "°C"
    // Return degrees in Fahrenheit.
    | "F" ->
        (temp * 9. / 5. + 32.).ToString("F2", provider) + "°F"
    // Return degrees in Kelvin.
    | "K" ->
        (temp + 273.15).ToString()
    | _ ->
        raise (FormatException(String.Format("The {0} format string is not supported.",format)))
Public Overloads Function ToString(fmt As String, provider As IFormatProvider) As String _
               Implements IFormattable.ToString
    If String.IsNullOrEmpty(fmt) Then fmt = "G"
    If provider Is Nothing Then provider = CultureInfo.CurrentCulture

    Select Case fmt.ToUpperInvariant()
     ' Return degrees in Celsius.    
        Case "G", "C"
            Return temp.ToString("F2", provider) + "°C"
     ' Return degrees in Fahrenheit.
        Case "F"
            Return (temp * 9 / 5 + 32).ToString("F2", provider) + "°F"
     ' Return degrees in Kelvin.
        Case "K"
            Return (temp + 273.15).ToString()
        Case Else
            Throw New FormatException(
              String.Format("The {0} format string is not supported.",
                            fmt))
    End Select
End Function

不可变性和 StringBuilder 类

对象 String 称为不可变(只读),因为创建对象后无法修改其值。 似乎修改 String 对象的方法实际上返回包含修改的新 String 对象。

由于字符串是不可变的,因此对单个字符串执行重复添加或删除操作的字符串操作例程可能会造成重大性能损失。 例如,以下代码使用随机数生成器创建一个字符串,该字符串的范围为 1000 个字符,0x0001 0x052F。 尽管代码似乎使用字符串串联将新字符追加到命名 str的现有字符串,但它实际上为每个串联操作创建一个新 String 对象。

using System;
using System.IO;
using System.Text;

public class Example6
{
   public static void Main()
   {
      Random rnd = new Random();
      
      string str = String.Empty;
      StreamWriter sw = new StreamWriter(@".\StringFile.txt", 
                           false, Encoding.Unicode);

      for (int ctr = 0; ctr <= 1000; ctr++) {
         str += (char)rnd.Next(1, 0x0530);
         if (str.Length % 60 == 0)
            str += Environment.NewLine;          
      }                    
      sw.Write(str);
      sw.Close();
   }
}
open System
open System.IO
open System.Text

do
    let rnd = Random()

    let mutable str = String.Empty
    use sw = new StreamWriter(@".\StringFile.txt", false, Encoding.Unicode)
    for _ = 0 to 1000 do
        str <- str + (rnd.Next(1, 0x0530) |> char |> string)
        if str.Length % 60 = 0 then
            str <- str + Environment.NewLine
    sw.Write str
Imports System.IO
Imports System.Text

Module Example10
    Public Sub Main()
        Dim rnd As New Random()

        Dim str As String = String.Empty
        Dim sw As New StreamWriter(".\StringFile.txt",
                           False, Encoding.Unicode)

        For ctr As Integer = 0 To 1000
            str += ChrW(rnd.Next(1, &H530))
            If str.Length Mod 60 = 0 Then str += vbCrLf
        Next
        sw.Write(str)
        sw.Close()
    End Sub
End Module

可以将 StringBuilder 类而不是 String 类用于对字符串值进行多次更改的操作。 与类的 String 实例不同, StringBuilder 对象是可变的;在字符串中连接、追加或删除子字符串时,对单个字符串执行操作。 完成修改对象的值 StringBuilder 后,可以调用其 StringBuilder.ToString 方法将其转换为字符串。 下面的示例将上一个示例中使用的 String 替换为 StringBuilder 对象,将范围从 0x0001 到 0x052F 的 1000 个随机字符连接起来。

using System;
using System.IO;
using System.Text;

public class Example10
{
   public static void Main()
   {
      Random rnd = new Random();
      StringBuilder sb = new StringBuilder();
      StreamWriter sw = new StreamWriter(@".\StringFile.txt", 
                                         false, Encoding.Unicode);

      for (int ctr = 0; ctr <= 1000; ctr++) {
         sb.Append((char)rnd.Next(1, 0x0530));
         if (sb.Length % 60 == 0)
            sb.AppendLine();          
      }                    
      sw.Write(sb.ToString());
      sw.Close();
   }
}
open System
open System.IO
open System.Text

do
    let rnd = Random()
    let sb = StringBuilder()
    use sw = new StreamWriter(@".\StringFile.txt", false, Encoding.Unicode)

    for _ = 0 to 1000 do
        sb.Append(rnd.Next(1, 0x0530) |> char) |> ignore
        if sb.Length % 60 = 0 then
            sb.AppendLine() |> ignore
    sw.Write(string sb)
Imports System.IO
Imports System.Text

Module Example11
    Public Sub Main()
        Dim rnd As New Random()
        Dim sb As New StringBuilder()
        Dim sw As New StreamWriter(".\StringFile.txt",
                                 False, Encoding.Unicode)

        For ctr As Integer = 0 To 1000
            sb.Append(ChrW(rnd.Next(1, &H530)))
            If sb.Length Mod 60 = 0 Then sb.AppendLine()
        Next
        sw.Write(sb.ToString())
        sw.Close()
    End Sub
End Module

按顺序操作与区分区域性的操作

String 类的成员对 String 对象执行按顺序操作或区分区域性(语言方面)的操作。 序号运算对每个 Char 对象的数值进行操作。 区分区域性的操作作用于 String 对象的值,并考虑区域性特定的大小写、排序、格式设置和解析规则。 区分区域性的操作结合显式声明的区域性或隐式的当前区域性执行。 当对同一字符串执行这些操作时,这两种类型的操作可能会产生截然不同的结果。

.NET 还通过使用固定区域性 (CultureInfo.InvariantCulture) 来支持不区分区域性的语言字符串操作,固定区域性大致基于独立于区域的英语语言的区域性设置。 与其他 System.Globalization.CultureInfo 设置不同,不变文化的设置保证在单个计算机上、从系统到系统以及跨 .NET 版本保持一致。 不可变文化可视为一种黑匣子,确保在所有文化中字符串比较和排序的稳定性。

重要

如果应用程序对符号性标识符(如文件名或命名管道)或持久化的数据(如 XML 文件中的文本数据)做出安全性决策,则操作应使用序号比较而不是区分文化的比较。 这是因为区分区域性的比较可能会根据生效的区域性产生不同的结果,而按顺序比较仅取决于所比较字符的二进制值。

重要

大多数执行字符串操作的方法都包含一个重载,该重载具有一个 StringComparison 类型的参数,这使你能够指定该方法执行的是按顺序操作还是区分区域性的操作。 一般来说,你应该调用该重载来明确你的方法调用意图。 有关使用序数和文化敏感性操作的字符串最佳做法和指南,请参阅 使用字符串的最佳做法和指南

关于大小写、解析和格式设置、比较和排序以及相等性测试的操作可以是按顺序的,也可以是区分区域性的。 以下各节讨论每个操作类别。

提示

你应该始终调用一个能使你的方法调用意图明确的方法重载。 例如,不要调用 Compare(String, String) 方法来使用当前区域性的约定对两个字符串执行区分区域性的比较,而应该调用 Compare(String, String, StringComparison) 方法,并为 comparisonType 参数传入值 StringComparison.CurrentCulture。 有关详细信息,请参阅有关使用字符串的最佳实践

可以从以下链接下载排序权重表、一组文本文件,其中包含有关排序和比较操作中使用的字符权重的信息:

大小写

大小写规则决定了如何更改 Unicode 字符的大小写形式,例如,从小写转换为大写。 通常,在进行字符串比较之前会先执行大小写转换操作。 例如,字符串可以转换为大写,以便可以将其与其他大写字符串进行比较。 可以通过调用ToLower或方法将字符串中的字符转换为小写,并且可以通过调用ToLowerInvariantToUpperToUpperInvariant方法将其转换为大写。 此外,你还可以使用 TextInfo.ToTitleCase 方法将字符串转换为词首字母大写样式。

注意

仅在 Linux 和 macOS 系统上运行的 .NET Core:C 和 Posix 区域性的排序规则行为始终区分大小写,因为这些区域性不使用预期的 Unicode 排序规则顺序。 建议使用除 C 或 Posix 以外的区域性执行区分区域性但不区分大小写的排序操作。

大小写转换操作可以基于当前区域性、指定区域性或固定区域性的规则来进行。 由于大小写映射可能会因所使用的区域性不同而有所差异,因此大小写转换操作的结果也可能会因区域性的不同而不同。 实际的大小写差异有三种类型:

  • 拉丁大写字母 I (U+0049)、拉丁小写字母 I (U+0069)、上面带点的拉丁大写字母 I (U+0130) 和不带点的拉丁小写字母 I (U+0131) 在大小写映射上的差异。 在 tr-TR(土耳其语(土耳其))和 az-Latn-AZ(阿塞拜疆,拉丁语)区域性中,以及在 tr、az 和 az-Latn 中性区域性中,拉丁大写字母 I 的小写形式是不带点的拉丁小写字母 I,拉丁小写字母 I 的大写形式是上面带点的拉丁大写字母 I。 在所有其他区域性中,包括固定区域性,拉丁小写字母 I 和拉丁大写字母 I 是相互对应的小写和大写形式。

    以下示例演示了如果依赖于区分区域性的大小写比较,旨在防止文件系统访问的字符串比较为什么可能会失败。 (本应使用固定区域性的大小写约定。)

    using System;
    using System.Globalization;
    using System.Threading;
    
    public class Example1
    {
       const string disallowed = "file";
       
       public static void Main()
       {
          IsAccessAllowed(@"FILE:\\\c:\users\user001\documents\FinancialInfo.txt");
       }
    
       private static void IsAccessAllowed(String resource)
       {
          CultureInfo[] cultures = { CultureInfo.CreateSpecificCulture("en-US"),
                                     CultureInfo.CreateSpecificCulture("tr-TR") };
          String scheme = null;
          int index = resource.IndexOfAny( new Char[] { '\\', '/' } );
          if (index > 0) 
             scheme = resource.Substring(0, index - 1);
    
          // Change the current culture and perform the comparison.
          foreach (var culture in cultures) {
             Thread.CurrentThread.CurrentCulture = culture;
             Console.WriteLine($"Culture: {CultureInfo.CurrentCulture.DisplayName}");
             Console.WriteLine(resource);
             Console.WriteLine($"Access allowed: {! String.Equals(disallowed, scheme, StringComparison.CurrentCultureIgnoreCase)}");      
             Console.WriteLine();
          }   
       }
    }
    // The example displays the following output:
    //       Culture: English (United States)
    //       FILE:\\\c:\users\user001\documents\FinancialInfo.txt
    //       Access allowed: False
    //       
    //       Culture: Turkish (Turkey)
    //       FILE:\\\c:\users\user001\documents\FinancialInfo.txt
    //       Access allowed: True
    
    open System
    open System.Globalization
    open System.Threading
    
    let disallowed = "file"
    
    let isAccessAllowed (resource: string) =
        let cultures = 
            [| CultureInfo.CreateSpecificCulture "en-US"
               CultureInfo.CreateSpecificCulture "tr-TR" |]
        let index = resource.IndexOfAny [| '\\'; '/' |]
        let scheme =
            if index > 0 then
                resource.Substring(0, index - 1)
            else 
                null
    
        // Change the current culture and perform the comparison.
        for culture in cultures do
            Thread.CurrentThread.CurrentCulture <- culture
            printfn $"Culture: {CultureInfo.CurrentCulture.DisplayName}"
            printfn $"{resource}"
            printfn $"Access allowed: {String.Equals(disallowed, scheme, StringComparison.CurrentCultureIgnoreCase) |> not}"
            printfn ""
            
    isAccessAllowed @"FILE:\\\c:\users\user001\documents\FinancialInfo.txt"
    // The example displays the following output:
    //       Culture: English (United States)
    //       FILE:\\\c:\users\user001\documents\FinancialInfo.txt
    //       Access allowed: False
    //
    //       Culture: Turkish (Turkey)
    //       FILE:\\\c:\users\user001\documents\FinancialInfo.txt
    //       Access allowed: True
    
    Imports System.Globalization
    Imports System.Threading
    
    Module Example2
        Const disallowed = "file"
    
        Public Sub Main()
            IsAccessAllowed("FILE:\\\c:\users\user001\documents\FinancialInfo.txt")
        End Sub
    
        Private Sub IsAccessAllowed(resource As String)
            Dim cultures() As CultureInfo = {CultureInfo.CreateSpecificCulture("en-US"),
                                            CultureInfo.CreateSpecificCulture("tr-TR")}
            Dim scheme As String = Nothing
            Dim index As Integer = resource.IndexOfAny({"\"c, "/"c})
            If index > 0 Then scheme = resource.Substring(0, index - 1)
    
            ' Change the current culture and perform the comparison.
            For Each culture In cultures
                Thread.CurrentThread.CurrentCulture = culture
                Console.WriteLine("Culture: {0}", CultureInfo.CurrentCulture.DisplayName)
                Console.WriteLine(resource)
                Console.WriteLine("Access allowed: {0}",
                               Not String.Equals(disallowed, scheme, StringComparison.CurrentCultureIgnoreCase))
                Console.WriteLine()
            Next
        End Sub
    End Module
    ' The example displays the following output:
    '       Culture: English (United States)
    '       FILE:\\\c:\users\user001\documents\FinancialInfo.txt
    '       Access allowed: False
    '       
    '       Culture: Turkish (Turkey)
    '       FILE:\\\c:\users\user001\documents\FinancialInfo.txt
    '       Access allowed: True
    
  • 固定区域性与所有其他区域性之间在大小写映射上的差异。 在这些情况下,使用固定区域性的大小写转换规则在将一个字符转换为大写或小写时,返回的是同一个字符。 而对于所有其他区域性,返回的则是一个不同的字符。 下表列出了一些受影响的字符。

    字符 如果更改为 返回
    微米符号 (U+00B5) Uppercase 希腊大写字母 MU (U+-39C)
    上面带点的拉丁大写字母 I (U+0130) Lowercase 拉丁文小写字母 I (U+0069)
    不带点的拉丁小写字母 I (U+0131) Uppercase 拉丁大写字母 I (U+0049)
    拉丁文小写字母 LONG S (U+017F) Uppercase 拉丁大写字母 S (U+0053)
    带小写字母 Z(含带变音符号)的拉丁大写字母 D (U+01C5) Lowercase 带变音符号的拉丁小写字母 DZ (U+01C6)
    组合希腊语下加线符号 (U+0345) Uppercase 希腊语大写字母 IOTA (U+0399)
  • ASCII 字符范围内由两个字母组成的大小写混合字母对在大小写映射上的差异。 在大多数区域性中,由两个字母组成的大小写混合字母对与对应的两个大写字母对或两个小写字母对是相等的。 但在以下区域性中,对于下列由两个字母组成的字母对来说并非如此,因为在每种情况下,它们都会与一个连字进行比较:

    • hr-HR(克罗地亚语(克罗地亚))区域性中的“lJ”和“nJ”。
    • cs-CZ(捷克语(捷克共和国))和 sk-SK(斯洛伐克语(斯洛伐克))区域性中的“cH”。
    • da-DK(丹麦语(丹麦))区域性中的“aA”。
    • hu-HU(匈牙利语(匈牙利))区域性中的“cS”、“dZ”、“dZS”、“nY”、“sZ”、“tY”和“zS”。
    • es-ES_tradnl(西班牙语(西班牙,传统排序))区域性中的“cH”和“lL”。
    • vi-VN(越南语(越南))区域性中的“cH”、“gI”、“kH”、“nG”、“nH”、“pH”、“qU”、“tH”和“tR”。

    不过,并不会经常遇到对这些字母对进行区分区域性的比较而产生问题的情况,因为这些字母对在固定字符串或标识符中并不常用。

以下示例说明了在将字符串转换为大写形式时,不同区域性之间在大小写规则方面的一些差异。

using System;
using System.Globalization;
using System.IO;

public class Example
{
   public static void Main()
   {
      StreamWriter sw = new StreamWriter(@".\case.txt");   
      string[] words = { "file", "sıfır", "Dženana" };
      CultureInfo[] cultures = { CultureInfo.InvariantCulture, 
                                 new CultureInfo("en-US"),  
                                 new CultureInfo("tr-TR") };

      foreach (var word in words) {
         sw.WriteLine("{0}:", word);
         foreach (var culture in cultures) {
            string name = String.IsNullOrEmpty(culture.Name) ? 
                                 "Invariant" : culture.Name;
            string upperWord = word.ToUpper(culture);
            sw.WriteLine("   {0,10}: {1,7} {2, 38}", name, 
                         upperWord, ShowHexValue(upperWord));
         }
         sw.WriteLine();  
      }
      sw.Close();
   }

   private static string ShowHexValue(string s)
   {
      string retval = null;
      foreach (var ch in s) {
         byte[] bytes = BitConverter.GetBytes(ch);
         retval += String.Format("{0:X2} {1:X2} ", bytes[1], bytes[0]);     
      }
      return retval;
   } 
}
// The example displays the following output:
//    file:
//        Invariant:    FILE               00 46 00 49 00 4C 00 45 
//            en-US:    FILE               00 46 00 49 00 4C 00 45 
//            tr-TR:    FİLE               00 46 01 30 00 4C 00 45 
//    
//    sıfır:
//        Invariant:   SıFıR         00 53 01 31 00 46 01 31 00 52 
//            en-US:   SIFIR         00 53 00 49 00 46 00 49 00 52 
//            tr-TR:   SIFIR         00 53 00 49 00 46 00 49 00 52 
//    
//    Dženana:
//        Invariant:  DžENANA   01 C5 00 45 00 4E 00 41 00 4E 00 41 
//            en-US:  DŽENANA   01 C4 00 45 00 4E 00 41 00 4E 00 41 
//            tr-TR:  DŽENANA   01 C4 00 45 00 4E 00 41 00 4E 00 41
open System
open System.Globalization
open System.IO

let showHexValue (s: string) =
    let mutable retval = ""
    for ch in s do
        let bytes = BitConverter.GetBytes ch
        retval <- retval + String.Format("{0:X2} {1:X2} ", bytes[1], bytes[0])
    retval

do
    use sw = new StreamWriter(@".\case.txt")
    let words = [| "file"; "sıfır"; "Dženana" |]
    let cultures = 
        [| CultureInfo.InvariantCulture 
           CultureInfo "en-US"
           CultureInfo "tr-TR" |]

    for word in words do
        sw.WriteLine("{0}:", word)
        for culture in cultures do
            let name =
                 if String.IsNullOrEmpty culture.Name then "Invariant" else culture.Name
            let upperWord = word.ToUpper culture
            sw.WriteLine("   {0,10}: {1,7} {2, 38}", name, upperWord, showHexValue upperWord)
        sw.WriteLine()
    sw.Close()

// The example displays the following output:
//    file:
//        Invariant:    FILE               00 46 00 49 00 4C 00 45
//            en-US:    FILE               00 46 00 49 00 4C 00 45
//            tr-TR:    FİLE               00 46 01 30 00 4C 00 45
//
//    sıfır:
//        Invariant:   SıFıR         00 53 01 31 00 46 01 31 00 52
//            en-US:   SIFIR         00 53 00 49 00 46 00 49 00 52
//            tr-TR:   SIFIR         00 53 00 49 00 46 00 49 00 52
//
//    Dženana:
//        Invariant:  DžENANA   01 C5 00 45 00 4E 00 41 00 4E 00 41
//            en-US:  DŽENANA   01 C4 00 45 00 4E 00 41 00 4E 00 41
//            tr-TR:  DŽENANA   01 C4 00 45 00 4E 00 41 00 4E 00 41
Imports System.Globalization
Imports System.IO

Module Example1
    Public Sub Main()
        Dim sw As New StreamWriter(".\case.txt")
        Dim words As String() = {"file", "sıfır", "Dženana"}
        Dim cultures() As CultureInfo = {CultureInfo.InvariantCulture,
                                        New CultureInfo("en-US"),
                                        New CultureInfo("tr-TR")}

        For Each word In words
            sw.WriteLine("{0}:", word)
            For Each culture In cultures
                Dim name As String = If(String.IsNullOrEmpty(culture.Name),
                                 "Invariant", culture.Name)
                Dim upperWord As String = word.ToUpper(culture)
                sw.WriteLine("   {0,10}: {1,7} {2, 38}", name,
                         upperWord, ShowHexValue(upperWord))

            Next
            sw.WriteLine()
        Next
        sw.Close()
    End Sub

    Private Function ShowHexValue(s As String) As String
        Dim retval As String = Nothing
        For Each ch In s
            Dim bytes() As Byte = BitConverter.GetBytes(ch)
            retval += String.Format("{0:X2} {1:X2} ", bytes(1), bytes(0))
        Next
        Return retval
    End Function
End Module
' The example displays the following output:
'    file:
'        Invariant:    FILE               00 46 00 49 00 4C 00 45 
'            en-US:    FILE               00 46 00 49 00 4C 00 45 
'            tr-TR:    FİLE               00 46 01 30 00 4C 00 45 
'    
'    sıfır:
'        Invariant:   SıFıR         00 53 01 31 00 46 01 31 00 52 
'            en-US:   SIFIR         00 53 00 49 00 46 00 49 00 52 
'            tr-TR:   SIFIR         00 53 00 49 00 46 00 49 00 52 
'    
'    Dženana:
'        Invariant:  DžENANA   01 C5 00 45 00 4E 00 41 00 4E 00 41 
'            en-US:  DŽENANA   01 C4 00 45 00 4E 00 41 00 4E 00 41 
'            tr-TR:  DŽENANA   01 C4 00 45 00 4E 00 41 00 4E 00 41

分析和格式设置

格式设置和分析是逆运算。 格式规则确定如何将值(如日期和时间或数字)转换为其字符串表示形式,而分析规则确定如何将字符串表示形式转换为日期和时间等值。 格式设置和分析规则都依赖于文化约定。 以下示例说明了解释区域性特定的日期字符串时可能出现的歧义性。 如果不知道生成日期字符串时使用的文化约定,就无法判断 03/01/2011、3/1/2011 和 01/03/2011 是表示 2011 年 1 月 3 日还是 2011 年 3 月 1 日。

using System;
using System.Globalization;

public class Example9
{
   public static void Main()
   {
      DateTime date = new DateTime(2011, 3, 1);
      CultureInfo[] cultures = { CultureInfo.InvariantCulture, 
                                 new CultureInfo("en-US"), 
                                 new CultureInfo("fr-FR") };

      foreach (var culture in cultures)
         Console.WriteLine("{0,-12} {1}", String.IsNullOrEmpty(culture.Name) ?
                           "Invariant" : culture.Name, 
                           date.ToString("d", culture));                                    
   }
}
// The example displays the following output:
//       Invariant    03/01/2011
//       en-US        3/1/2011
//       fr-FR        01/03/2011
open System
open System.Globalization

let date = DateTime(2011, 3, 1)
let cultures = 
      [| CultureInfo.InvariantCulture
         CultureInfo "en-US"
         CultureInfo "fr-FR" |]

for culture in cultures do
    printfn $"""{(if String.IsNullOrEmpty culture.Name then "Invariant" else culture.Name),-12} {date.ToString("d", culture)}"""
// The example displays the following output:
//       Invariant    03/01/2011
//       en-US        3/1/2011
//       fr-FR        01/03/2011
Imports System.Globalization

Module Example8
    Public Sub Main()
        Dim dat As Date = #3/1/2011#
        Dim cultures() As CultureInfo = {CultureInfo.InvariantCulture,
                                        New CultureInfo("en-US"),
                                        New CultureInfo("fr-FR")}

        For Each culture In cultures
            Console.WriteLine("{0,-12} {1}", If(String.IsNullOrEmpty(culture.Name),
                           "Invariant", culture.Name),
                           dat.ToString("d", culture))
        Next
    End Sub
End Module
' The example displays the following output:
'       Invariant    03/01/2011
'       en-US        3/1/2011
'       fr-FR        01/03/2011

同样,如以下示例所示,单个字符串可以根据解析操作中采用的文化习惯,产生不同的日期。

using System;
using System.Globalization;

public class Example15
{
   public static void Main()
   {
      string dateString = "07/10/2011";
      CultureInfo[] cultures = { CultureInfo.InvariantCulture, 
                                 CultureInfo.CreateSpecificCulture("en-GB"), 
                                 CultureInfo.CreateSpecificCulture("en-US") };
      Console.WriteLine("{0,-12} {1,10} {2,8} {3,8}\n", "Date String", "Culture", 
                                                 "Month", "Day");
      foreach (var culture in cultures) {
         DateTime date = DateTime.Parse(dateString, culture);
         Console.WriteLine("{0,-12} {1,10} {2,8} {3,8}", dateString, 
                           String.IsNullOrEmpty(culture.Name) ?
                           "Invariant" : culture.Name, 
                           date.Month, date.Day);
      }                      
   }
}
// The example displays the following output:
//       Date String     Culture    Month      Day
//       
//       07/10/2011    Invariant        7       10
//       07/10/2011        en-GB       10        7
//       07/10/2011        en-US        7       10
open System
open System.Globalization

let dateString = "07/10/2011"
let cultures = 
    [| CultureInfo.InvariantCulture
       CultureInfo.CreateSpecificCulture "en-GB"
       CultureInfo.CreateSpecificCulture "en-US" |]
printfn $"""{"Date String",-12} {"Culture",10} {"Month",8} {"Day",8}\n"""
for culture in cultures do
    let date = DateTime.Parse(dateString, culture)
    printfn $"""{dateString,-12} {(if String.IsNullOrEmpty culture.Name then "Invariant" else culture.Name),10} {date.Month,8} {date.Day,8}"""
// The example displays the following output:
//       Date String     Culture    Month      Day
//
//       07/10/2011    Invariant        7       10
//       07/10/2011        en-GB       10        7
//       07/10/2011        en-US        7       10
Imports System.Globalization

Module Example18
    Public Sub Main()
        Dim dateString As String = "07/10/2011"
        Dim cultures() As CultureInfo = {CultureInfo.InvariantCulture,
                                        CultureInfo.CreateSpecificCulture("en-GB"),
                                        CultureInfo.CreateSpecificCulture("en-US")}
        Console.WriteLine("{0,-12} {1,10} {2,8} {3,8}", "Date String", "Culture",
                                                 "Month", "Day")
        Console.WriteLine()
        For Each culture In cultures
            Dim dat As Date = DateTime.Parse(dateString, culture)
            Console.WriteLine("{0,-12} {1,10} {2,8} {3,8}", dateString,
                           If(String.IsNullOrEmpty(culture.Name),
                           "Invariant", culture.Name),
                           dat.Month, dat.Day)
        Next
    End Sub
End Module
' The example displays the following output:
'       Date String     Culture    Month      Day
'       
'       07/10/2011    Invariant        7       10
'       07/10/2011        en-GB       10        7
'       07/10/2011        en-US        7       10

字符串比较和排序

比较和排序字符串的约定因区域性而异。 例如,排序顺序可能基于拼音或字符的可视表示形式。 在东亚语言中,按文字的笔画和部首对字符进行排序。 排序还取决于语言和文化所使用的字母表顺序。 例如,丹麦语有一个“Æ”字符,它在字母表中排在“Z”之后。 此外,比较操作可以区分大小写,也可以不区分大小写,而且大小写规则可能因区域性的不同而有所不同。 另一方面,序数比较在比较和排序字符串时,使用字符串中每个字符的 Unicode 码点。

排序规则确定 Unicode 字符的字母顺序以及两个字符串的相互比较方式。 例如,该方法 String.Compare(String, String, StringComparison) 基于 StringComparison 参数比较两个字符串。 如果参数值为 StringComparison.CurrentCulture,则该方法执行使用当前区域性约定的语言比较,如果参数值为 StringComparison.Ordinal,则该方法执行按顺序比较。 因此,如以下示例所示,如果当前文化为美国英语,则对 String.Compare(String, String, StringComparison) 方法进行首次调用(使用文化敏感比较)时,“a”被认为比“A”小,但使用相同方法的第二次调用(使用序数比较)时,则认为“a”大于“A”。

using System;
using System.Globalization;
using System.Threading;

public class Example2
{
   public static void Main()
   {
      Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
      Console.WriteLine(String.Compare("A", "a", StringComparison.CurrentCulture));
      Console.WriteLine(String.Compare("A", "a", StringComparison.Ordinal));
   }
}
// The example displays the following output:
//       1
//       -32
open System
open System.Globalization
open System.Threading

Thread.CurrentThread.CurrentCulture <- CultureInfo.CreateSpecificCulture "en-US"
printfn $"""{String.Compare("A", "a", StringComparison.CurrentCulture)}"""
printfn $"""{String.Compare("A", "a", StringComparison.Ordinal)}"""
// The example displays the following output:
//       1
//       -32
Imports System.Globalization
Imports System.Threading

Module Example3
    Public Sub Main()
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        Console.WriteLine(String.Compare("A", "a", StringComparison.CurrentCulture))
        Console.WriteLine(String.Compare("A", "a", StringComparison.Ordinal))
    End Sub
End Module
' The example displays the following output:
'       1                                                                                     
'       -32

.NET 支持单词、字符串和序号排序规则:

  • 单词排序会对字符串执行区分区域性的比较,在这种比较中,某些非字母数字的 Unicode 字符可能会被赋予特殊的权重。 例如,连字符(-)的权重可能被设置得非常小,以便“coop”和“co-op”在排序列表中彼此相邻出现。 有关使用单词排序规则比较两个字符串的方法的列表String,请参阅“按类别划分的字符串操作”部分。

  • 字符串排序同样也会执行区分区域性的比较。 它类似于单词排序,只是没有特殊情况,所有非字母数字符号都位于所有字母数字 Unicode 字符之前。 可以通过调用 CompareInfo.Compare 方法的重载来依据字符串排序规则对两个字符串进行比较,这些重载有一个 options 参数,为其传入值 CompareOptions.StringSort 即可。 请注意,这是 .NET 提供的唯一方法,用于使用字符串排序规则比较两个字符串。

  • 序号排序根据字符串中每个 Char 对象的数值比较字符串。 按顺序比较会自动区分大小写,因为一个字符的小写形式和大写形式具有不同的代码点。 但是,如果事例不重要,可以指定忽略事例的序号比较。 这相当于使用固定区域性将字符串转换为大写形式,然后对转换后的结果执行按顺序比较。 有关使用序号排序规则比较两个字符串的方法的列表String,请参阅“按类别划分的字符串操作”部分。

文化敏感比较是显式或隐式使用 CultureInfo 对象的任意比较,包括由 CultureInfo.InvariantCulture 属性指定的不变的文化。 隐式文化是当前的文化,由 Thread.CurrentCultureCultureInfo.CurrentCulture 属性指定。 字母字符(即其Char.IsLetter属性返回true的字符)的排序顺序在不同文化中存在相当大的变化。 你可以通过向诸如 Compare(String, String, CultureInfo, CompareOptions) 这样的字符串比较方法提供一个 CultureInfo 对象,来指定一种使用特定区域性约定的区分区域性的比较。 你可以通过向 Compare 方法的相应重载传入 StringComparison.CurrentCultureStringComparison.CurrentCultureIgnoreCase,或者传入 CompareOptions 枚举中除 CompareOptions.OrdinalCompareOptions.OrdinalIgnoreCase 之外的任意成员,来指定一个使用当前区域性约定的区分区域性的比较。 文化敏感的比较通常适用于排序,而序号比较则不适用。 按顺序比较通常适合用于确定两个字符串是否相等(也就是说,用于确定它们是否完全相同),而区分区域性的比较则不适合。

以下例子说明了文化相关的比较和序号比较之间的差异。 下面这个示例使用按顺序比较以及 da-DK 和 en-US 区域性的约定(在调用 Compare 方法时,这两种区域性各自都是默认区域性)来评估三个字符串“Apple”、“Æble”和“AEble”。 由于丹麦语将字符“Æ”视为单个字母,并在字母表的“Z”后对其进行排序,因此字符串“Æble”大于“Apple”。 但是,“Æble”不被视为等效于“AEble”,因此“Æble”也大于“AEble”。 en-US 文化不包括字母“Æ”,但将其视为等效于“AE”,这解释了为什么“Æble”小于“Apple”,但等于“AEble”。 另一方面,按顺序比较会认为“Apple”小于“Æble”,而“Æble”大于“AEble”。

using System;
using System.Globalization;
using System.Threading;

public class CompareStringSample
{
   public static void Main()
   {
      string str1 = "Apple";
      string str2 = "Æble"; 
      string str3 = "AEble";
      
      // Set the current culture to Danish in Denmark.
      Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
      Console.WriteLine($"Current culture: {CultureInfo.CurrentCulture.Name}");
      Console.WriteLine($"Comparison of {str1} with {str2}: {String.Compare(str1, str2)}");
      Console.WriteLine($"Comparison of {str2} with {str3}: {String.Compare(str2, str3)}\n");
      
      // Set the current culture to English in the U.S.
      Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
      Console.WriteLine($"Current culture: {CultureInfo.CurrentCulture.Name}");
      Console.WriteLine($"Comparison of {str1} with {str2}: {String.Compare(str1, str2)}");
      Console.WriteLine($"Comparison of {str2} with {str3}: {String.Compare(str2, str3)}\n");
      
      // Perform an ordinal comparison.
      Console.WriteLine("Ordinal comparison");
      Console.WriteLine($"Comparison of {str1} with {str2}: {String.Compare(str1, str2, StringComparison.Ordinal)}");
      Console.WriteLine($"Comparison of {str2} with {str3}: {String.Compare(str2, str3, StringComparison.Ordinal)}");
   }
}
// The example displays the following output:
//       Current culture: da-DK
//       Comparison of Apple with Æble: -1
//       Comparison of Æble with AEble: 1
//       
//       Current culture: en-US
//       Comparison of Apple with Æble: 1
//       Comparison of Æble with AEble: 0
//       
//       Ordinal comparison
//       Comparison of Apple with Æble: -133
//       Comparison of Æble with AEble: 133
open System
open System.Globalization
open System.Threading

let str1 = "Apple"
let str2 = "Æble"
let str3 = "AEble"

// Set the current culture to Danish in Denmark.
Thread.CurrentThread.CurrentCulture <- CultureInfo "da-DK"
printfn $"Current culture: {CultureInfo.CurrentCulture.Name}"
printfn $"Comparison of {str1} with {str2}: {String.Compare(str1, str2)}"
printfn $"Comparison of {str2} with {str3}: {String.Compare(str2, str3)}\n"

// Set the current culture to English in the U.S.
Thread.CurrentThread.CurrentCulture <- CultureInfo "en-US"
printfn $"Current culture: {CultureInfo.CurrentCulture.Name}"
printfn $"Comparison of {str1} with {str2}: {String.Compare(str1, str2)}"
printfn $"Comparison of {str2} with {str3}: {String.Compare(str2, str3)}\n"

// Perform an ordinal comparison.
printfn "Ordinal comparison"
printfn $"Comparison of {str1} with {str2}: {String.Compare(str1, str2, StringComparison.Ordinal)}"
printfn $"Comparison of {str2} with {str3}: {String.Compare(str2, str3, StringComparison.Ordinal)}"
// The example displays the following output:
//       Current culture: da-DK
//       Comparison of Apple with Æble: -1
//       Comparison of Æble with AEble: 1
//
//       Current culture: en-US
//       Comparison of Apple with Æble: 1
//       Comparison of Æble with AEble: 0
//
//       Ordinal comparison
//       Comparison of Apple with Æble: -133
//       Comparison of Æble with AEble: 133
Imports System.Globalization
Imports System.Threading

Public Module Example6
    Public Sub Main()
        Dim str1 As String = "Apple"
        Dim str2 As String = "Æble"
        Dim str3 As String = "AEble"

        ' Set the current culture to Danish in Denmark.
        Thread.CurrentThread.CurrentCulture = New CultureInfo("da-DK")
        Console.WriteLine("Current culture: {0}",
                        CultureInfo.CurrentCulture.Name)
        Console.WriteLine("Comparison of {0} with {1}: {2}",
                        str1, str2, String.Compare(str1, str2))
        Console.WriteLine("Comparison of {0} with {1}: {2}",
                        str2, str3, String.Compare(str2, str3))
        Console.WriteLine()

        ' Set the current culture to English in the U.S.
        Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
        Console.WriteLine("Current culture: {0}",
                        CultureInfo.CurrentCulture.Name)
        Console.WriteLine("Comparison of {0} with {1}: {2}",
                        str1, str2, String.Compare(str1, str2))
        Console.WriteLine("Comparison of {0} with {1}: {2}",
                        str2, str3, String.Compare(str2, str3))
        Console.WriteLine()

        ' Perform an ordinal comparison.
        Console.WriteLine("Ordinal comparison")
        Console.WriteLine("Comparison of {0} with {1}: {2}",
                        str1, str2,
                        String.Compare(str1, str2, StringComparison.Ordinal))
        Console.WriteLine("Comparison of {0} with {1}: {2}",
                        str2, str3,
                        String.Compare(str2, str3, StringComparison.Ordinal))
    End Sub
End Module
' The example displays the following output:
'       Current culture: da-DK
'       Comparison of Apple with Æble: -1
'       Comparison of Æble with AEble: 1
'       
'       Current culture: en-US
'       Comparison of Apple with Æble: 1
'       Comparison of Æble with AEble: 0
'       
'       Ordinal comparison
'       Comparison of Apple with Æble: -133
'       Comparison of Æble with AEble: 133

使用以下常规准则选择适当的排序或字符串比较方法:

  • 如果希望根据用户的区域性对字符串进行排序,则应根据当前区域性的约定对字符串进行排序。 如果用户的区域性发生更改,排序字符串的顺序也会相应地更改。 例如,同义词库应用程序应始终根据用户的区域性对字词进行排序。

  • 如果希望根据特定区域性的约定对字符串进行排序,则应通过向比较方法提供 CultureInfo 表示该区域性的对象来对字符串进行排序。 例如,在旨在教授学生特定语言的应用程序中,需要根据语言之一文化的约定对字符串进行排序。

  • 如果你希望字符串的顺序在不同区域性中保持不变,那么你应该根据固定区域性的约定对它们进行排序,或者使用按顺序比较。 例如,你可以使用按顺序排序来整理文件、进程、互斥体或命名管道的名称。

  • 对于涉及安全决策(例如判断用户名是否有效)的比较,应始终通过调用Equals方法的重载来按顺序进行相等性测试。

注意

字符串比较中使用的区分区域性的排序和大小写规则取决于 .NET 的版本。 在 .NET Core 上,字符串比较取决于基础操作系统支持的 Unicode 标准版本。 在 Windows 8 或更高版本上运行的 .NET Framework 4.5 及更高版本中,排序、大小写、规范化和 Unicode 字符信息符合 Unicode 6.0 标准。 在其他 Windows 操作系统上,它们符合 Unicode 5.0 标准。

有关单词、字符串和序号排序规则的详细信息,请参阅 System.Globalization.CompareOptions 主题。 有关何时使用每个规则的其他建议,请参阅 有关使用字符串的最佳做法。

通常,不调用字符串比较方法,例如 Compare 直接确定字符串的排序顺序。 相反,比较方法是通过排序方法(如 Array.SortList<T>.Sort) 调用的。 下面的示例执行四种不同的排序操作(使用当前区域性进行单词排序、使用固定区域性进行单词排序、序号排序和使用固定区域性进行字符串排序),而无需显式调用字符串比较方法,尽管它们确实指定要使用的比较类型。 请注意,每种类型的排序在其数组中生成字符串的唯一顺序。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
 
public class Example3
{
   public static void Main()
   {
      string[] strings = { "coop", "co-op", "cooperative", 
                           "co\u00ADoperative", "cœur", "coeur" };

      // Perform a word sort using the current (en-US) culture.
      string[] current = new string[strings.Length]; 
      strings.CopyTo(current, 0); 
      Array.Sort(current, StringComparer.CurrentCulture);

      // Perform a word sort using the invariant culture.
      string[] invariant = new string[strings.Length];
      strings.CopyTo(invariant, 0); 
      Array.Sort(invariant, StringComparer.InvariantCulture);

      // Perform an ordinal sort.
      string[] ordinal = new string[strings.Length];
      strings.CopyTo(ordinal, 0); 
      Array.Sort(ordinal, StringComparer.Ordinal);

      // Perform a string sort using the current culture.
      string[] stringSort = new string[strings.Length];
      strings.CopyTo(stringSort, 0); 
      Array.Sort(stringSort, new SCompare());

      // Display array values
      Console.WriteLine("{0,13} {1,13} {2,15} {3,13} {4,13}\n", 
                        "Original", "Word Sort", "Invariant Word", 
                        "Ordinal Sort", "String Sort");
      for (int ctr = 0; ctr < strings.Length; ctr++)
         Console.WriteLine("{0,13} {1,13} {2,15} {3,13} {4,13}", 
                           strings[ctr], current[ctr], invariant[ctr], 
                           ordinal[ctr], stringSort[ctr] );          
   }
}

// IComparer<String> implementation to perform string sort.
internal class SCompare : IComparer<String>
{
   public int Compare(string x, string y)
   {
      return CultureInfo.CurrentCulture.CompareInfo.Compare(x, y, CompareOptions.StringSort);
   }
}
// The example displays the following output:
//         Original     Word Sort  Invariant Word  Ordinal Sort   String Sort
//    
//             coop          cœur            cœur         co-op         co-op
//            co-op         coeur           coeur         coeur          cœur
//      cooperative          coop            coop          coop         coeur
//     co­operative         co-op           co-op   cooperative          coop
//             cœur   cooperative     cooperative  co­operative   cooperative
//            coeur  co­operative    co­operative          cœur  co­operative
open System
open System.Collections.Generic
open System.Globalization

// IComparer<String> implementation to perform string sort using an F# object expression.
let scompare = 
    { new IComparer<String> with
        member _.Compare(x, y) =
            CultureInfo.CurrentCulture.CompareInfo.Compare(x, y, CompareOptions.StringSort) }

let strings = [| "coop"; "co-op"; "cooperative"; "co\u00ADoperative"; "cœur"; "coeur" |]

// Perform a word sort using the current (en-US) culture.
let current = Array.copy strings
Array.Sort(current, StringComparer.CurrentCulture)

// Perform a word sort using the invariant culture.
let invariant = Array.copy strings
Array.Sort(invariant, StringComparer.InvariantCulture)

// Perform an ordinal sort.
let ordinal = Array.copy strings
Array.Sort(ordinal, StringComparer.Ordinal)

// Perform a string sort using the current culture.
let stringSort = Array.copy strings
Array.Sort(stringSort, scompare)

// Display array values
printfn "%13s %13s %15s %13s %13s\n" "Original" "Word Sort" "Invariant Word" "Ordinal Sort" "String Sort"
for i = 0 to strings.Length - 1 do
    printfn "%13s %13s %15s %13s %13s\n" strings[i] current[i] invariant[i] ordinal[i] stringSort[i]

// The example displays the following output:
//         Original     Word Sort  Invariant Word  Ordinal Sort   String Sort
//
//             coop          cœur            cœur         co-op         co-op
//            co-op         coeur           coeur         coeur          cœur
//      cooperative          coop            coop          coop         coeur
//     co­operative         co-op           co-op   cooperative          coop
//             cœur   cooperative     cooperative  co­operative   cooperative
//            coeur  co­operative    co­operative          cœur  co­operative
Imports System.Collections
Imports System.Collections.Generic
Imports System.Globalization

Module Example4
    Public Sub Main()
        Dim strings() As String = {"coop", "co-op", "cooperative",
                                  "co" + ChrW(&HAD) + "operative",
                                  "cœur", "coeur"}

        ' Perform a word sort using the current (en-US) culture.
        Dim current(strings.Length - 1) As String
        strings.CopyTo(current, 0)
        Array.Sort(current, StringComparer.CurrentCulture)

        ' Perform a word sort using the invariant culture.
        Dim invariant(strings.Length - 1) As String
        strings.CopyTo(invariant, 0)
        Array.Sort(invariant, StringComparer.InvariantCulture)

        ' Perform an ordinal sort.
        Dim ordinal(strings.Length - 1) As String
        strings.CopyTo(ordinal, 0)
        Array.Sort(ordinal, StringComparer.Ordinal)

        ' Perform a string sort using the current culture.
        Dim stringSort(strings.Length - 1) As String
        strings.CopyTo(stringSort, 0)
        Array.Sort(stringSort, New SCompare())

        ' Display array values
        Console.WriteLine("{0,13} {1,13} {2,15} {3,13} {4,13}",
                        "Original", "Word Sort", "Invariant Word",
                        "Ordinal Sort", "String Sort")
        Console.WriteLine()

        For ctr As Integer = 0 To strings.Length - 1
            Console.WriteLine("{0,13} {1,13} {2,15} {3,13} {4,13}",
                           strings(ctr), current(ctr), invariant(ctr),
                           ordinal(ctr), stringSort(ctr))
        Next
    End Sub
End Module

' IComparer<String> implementation to perform string sort.
Friend Class SCompare : Implements IComparer(Of String)
   Public Function Compare(x As String, y As String) As Integer _
                   Implements IComparer(Of String).Compare
      Return CultureInfo.CurrentCulture.CompareInfo.Compare(x, y, CompareOptions.StringSort)
   End Function
End Class
' The example displays the following output:
'         Original     Word Sort  Invariant Word  Ordinal Sort   String Sort
'    
'             coop          cœur            cœur         co-op         co-op
'            co-op         coeur           coeur         coeur          cœur
'      cooperative          coop            coop          coop         coeur
'     co­operative         co-op           co-op   cooperative          coop
'             cœur   cooperative     cooperative  co­operative   cooperative
'            coeur  co­operative    co­operative          cœur  co­operative

提示

在内部,.NET 使用排序键来支持区域性敏感的字符串比较。 对于字符串中的每个字符,都赋予若干类排序权重,包括字母、大小写和变音符。 由类表示的 SortKey 排序键为特定字符串提供这些权重的存储库。 如果应用在同一组字符串上执行大量搜索或排序操作,则可以通过为它使用的所有字符串生成和存储排序键来提高其性能。 当需要排序或比较操作时,可以使用排序键而不是字符串。 有关更多信息,请参见 SortKey 类。

如果你没有指定字符串比较约定,像 Array.Sort(Array) 这样的排序方法会对字符串执行区分区域性且区分大小写的排序。 下面的示例演示了更改当前文化如何影响数组中字符串的排序顺序。 它创建三个字符串的数组。 首先,它将 System.Threading.Thread.CurrentThread.CurrentCulture 属性设置为 en-US,并调用 Array.Sort(Array) 方法。 最终的排序顺序是基于英语(美国)区域性的排序约定。 接着,此示例将 System.Threading.Thread.CurrentThread.CurrentCulture 属性设置为 da-DK 并再次调用 Array.Sort 方法。 请注意,最终排序顺序与使用 en-US 时的结果不一样,因为这次使用的是针对丹麦语(丹麦)的排序约定。

using System;
using System.Globalization;
using System.Threading;

public class ArraySort
{
   public static void Main(String[] args)
   {
      // Create and initialize a new array to store the strings.
      string[] stringArray = { "Apple", "Æble", "Zebra"};

      // Display the values of the array.
      Console.WriteLine( "The original string array:");
      PrintIndexAndValues(stringArray);

      // Set the CurrentCulture to "en-US".
      Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
      // Sort the values of the array.
      Array.Sort(stringArray);

      // Display the values of the array.
      Console.WriteLine("After sorting for the culture \"en-US\":");
      PrintIndexAndValues(stringArray);

      // Set the CurrentCulture to "da-DK".
      Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
      // Sort the values of the Array.
      Array.Sort(stringArray);

      // Display the values of the array.
      Console.WriteLine("After sorting for the culture \"da-DK\":");
      PrintIndexAndValues(stringArray);
   }
   public static void PrintIndexAndValues(string[] myArray)
   {
      for (int i = myArray.GetLowerBound(0); i <=
            myArray.GetUpperBound(0); i++ )
         Console.WriteLine($"[{i}]: {myArray[i]}");
      Console.WriteLine();
   }
}
// The example displays the following output:
//       The original string array:
//       [0]: Apple
//       [1]: Æble
//       [2]: Zebra
//
//       After sorting for the "en-US" culture:
//       [0]: Æble
//       [1]: Apple
//       [2]: Zebra
//
//       After sorting for the culture "da-DK":
//       [0]: Apple
//       [1]: Zebra
//       [2]: Æble
open System
open System.Globalization
open System.Threading

let printIndexAndValues (myArray: string[]) =
    for i = myArray.GetLowerBound 0 to myArray.GetUpperBound 0 do
        printfn $"[{i}]: {myArray[i]}" 
    printfn ""

// Create and initialize a new array to store the strings.
let stringArray = [| "Apple"; "Æble"; "Zebra" |]

// Display the values of the array.
printfn "The original string array:"
printIndexAndValues stringArray

// Set the CurrentCulture to "en-US".
Thread.CurrentThread.CurrentCulture <- CultureInfo "en-US"
// Sort the values of the array.
Array.Sort stringArray

// Display the values of the array.
printfn "After sorting for the culture \"en-US\":"
printIndexAndValues stringArray

// Set the CurrentCulture to "da-DK".
Thread.CurrentThread.CurrentCulture <- CultureInfo "da-DK"
// Sort the values of the Array.
Array.Sort stringArray

// Display the values of the array.
printfn "After sorting for the culture \"da-DK\":"
printIndexAndValues stringArray
// The example displays the following output:
//       The original string array:
//       [0]: Apple
//       [1]: Æble
//       [2]: Zebra
//
//       After sorting for the "en-US" culture:
//       [0]: Æble
//       [1]: Apple
//       [2]: Zebra
//
//       After sorting for the culture "da-DK":
//       [0]: Apple
//       [1]: Zebra
//       [2]: Æble
Imports System.Globalization
Imports System.IO
Imports System.Threading

Public Class TextToFile   
   Public Shared Sub Main()
      ' Creates and initializes a new array to store 
      ' these date/time objects.
      Dim stringArray() As String = { "Apple", "Æble", "Zebra"}
      
      ' Displays the values of the array.
      Console.WriteLine("The original string array:")
      PrintIndexAndValues(stringArray)
      
      ' Set the CurrentCulture to "en-US".
      Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
      ' Sort the values of the Array.
      Array.Sort(stringArray)
      
      ' Display the values of the array.
      Console.WriteLine("After sorting for the ""en-US"" culture:")
      PrintIndexAndValues(stringArray)
      
      ' Set the CurrentCulture to "da-DK".
      Thread.CurrentThread.CurrentCulture = New CultureInfo("da-DK")
      ' Sort the values of the Array.
      Array.Sort(stringArray)
      
      ' Displays the values of the Array.
      Console.WriteLine("After sorting for the culture ""da-DK"":")
      PrintIndexAndValues(stringArray)
   End Sub

   Public Shared Sub PrintIndexAndValues(myArray() As String)
      For i As Integer = myArray.GetLowerBound(0) To myArray.GetUpperBound(0)
         Console.WriteLine("[{0}]: {1}", i, myArray(i))
      Next
      Console.WriteLine()
   End Sub 
End Class
' The example displays the following output:
'       The original string array:
'       [0]: Apple
'       [1]: Æble
'       [2]: Zebra
'       
'       After sorting for the "en-US" culture:
'       [0]: Æble
'       [1]: Apple
'       [2]: Zebra
'       
'       After sorting for the culture "da-DK":
'       [0]: Apple
'       [1]: Zebra
'       [2]: Æble

警告

如果比较字符串的主要目的是确定字符串是否相等,则应调用该方法 String.Equals 。 通常,你应该使用 Equals 来执行序号比较。 该方法 String.Compare 主要用于对字符串进行排序。

字符串搜索方法(如 String.StartsWithString.IndexOf)还可以执行考虑到文化差异或次序的字符串比较。 下面的示例使用 IndexOf 方法演示了序数和文化敏感性比较之间的差异。 如果当前区域性是英语(美国),区分区域性的搜索会认为子字符串“oe”与连字“œ”匹配。 由于软连字符(U+00AD)是一个零宽度字符,因此搜索会将软连字符视为等效 String.Empty 项,并在字符串开头查找匹配项。 另一方面,序号搜索在任一情况下都找不到匹配项。

using System;

public class Example8
{
   public static void Main()
   {
      // Search for "oe" and "œu" in "œufs" and "oeufs".
      string s1 = "œufs";
      string s2 = "oeufs";
      FindInString(s1, "oe", StringComparison.CurrentCulture);
      FindInString(s1, "oe", StringComparison.Ordinal);
      FindInString(s2, "œu", StringComparison.CurrentCulture);
      FindInString(s2, "œu", StringComparison.Ordinal);
      Console.WriteLine();
      
      string s3 = "co\u00ADoperative";
      FindInString(s3, "\u00AD", StringComparison.CurrentCulture);
      FindInString(s3, "\u00AD", StringComparison.Ordinal);
   }

   private static void FindInString(string s, string substring, StringComparison options)
   {
      int result = s.IndexOf(substring, options);
      if (result != -1)
         Console.WriteLine($"'{substring}' found in {s} at position {result}");
      else
         Console.WriteLine($"'{substring}' not found in {s}");                                                  
   }
}
// The example displays the following output:
//       'oe' found in œufs at position 0
//       'oe' not found in œufs
//       'œu' found in oeufs at position 0
//       'œu' not found in oeufs
//       
//       '­' found in co­operative at position 0
//       '­' found in co­operative at position 2
open System

let findInString (s: string) (substring: string) (options: StringComparison) =
    let result = s.IndexOf(substring, options)
    if result <> -1 then
        printfn $"'{substring}' found in {s} at position {result}"
    else
        printfn $"'{substring}' not found in {s}"

// Search for "oe" and "œu" in "œufs" and "oeufs".
let s1 = "œufs"
let s2 = "oeufs"
findInString s1 "oe" StringComparison.CurrentCulture
findInString s1 "oe" StringComparison.Ordinal
findInString s2 "œu" StringComparison.CurrentCulture
findInString s2 "œu" StringComparison.Ordinal
printfn ""

let s3 = "co\u00ADoperative"
findInString s3 "\u00AD" StringComparison.CurrentCulture
findInString s3 "\u00AD" StringComparison.Ordinal

// The example displays the following output:
//       'oe' found in œufs at position 0
//       'oe' not found in œufs
//       'œu' found in oeufs at position 0
//       'œu' not found in oeufs
//
//       '­' found in co­operative at position 0
//       '­' found in co­operative at position 2
Module Example5
    Public Sub Main()
        ' Search for "oe" and "œu" in "œufs" and "oeufs".
        Dim s1 As String = "œufs"
        Dim s2 As String = "oeufs"
        FindInString(s1, "oe", StringComparison.CurrentCulture)
        FindInString(s1, "oe", StringComparison.Ordinal)
        FindInString(s2, "œu", StringComparison.CurrentCulture)
        FindInString(s2, "œu", StringComparison.Ordinal)
        Console.WriteLine()

        Dim softHyphen As String = ChrW(&HAD)
        Dim s3 As String = "co" + softHyphen + "operative"
        FindInString(s3, softHyphen, StringComparison.CurrentCulture)
        FindInString(s3, softHyphen, StringComparison.Ordinal)
    End Sub

    Private Sub FindInString(s As String, substring As String,
                            options As StringComparison)
        Dim result As Integer = s.IndexOf(substring, options)
        If result <> -1 Then
            Console.WriteLine("'{0}' found in {1} at position {2}",
                           substring, s, result)
        Else
            Console.WriteLine("'{0}' not found in {1}",
                           substring, s)
        End If
    End Sub
End Module
' The example displays the following output:
'       'oe' found in œufs at position 0
'       'oe' not found in œufs
'       'œu' found in oeufs at position 0
'       'œu' not found in oeufs
'       
'       '­' found in co­operative at position 0
'       '­' found in co­operative at position 2

在字符串中搜索

字符串搜索方法(如 String.StartsWithString.IndexOf)还可以执行文化敏感的或按序的字符串比较,以确定是否在指定字符串中找到了某个字符或子字符串。

String 类中的搜索方法,比如搜索单个字符的 IndexOf 方法,或搜索一组字符的 IndexOfAny 方法,均执行按序搜索。 若要对字符执行区分区域性的搜索,必须调用 CompareInfo 方法,例如 CompareInfo.IndexOf(String, Char)CompareInfo.LastIndexOf(String, Char)。 请注意,使用序数形式和文化敏感性比较来搜索字符的结果可能会大相径庭。 例如,对一个预组合的 Unicode 字符(比如连字“Æ”(U+00C6))进行搜索时,根据区域性的不同,可能会匹配其组成部分以正确顺序出现的任何情况,比如“AE”(U+041U+0045)。 以下示例说明了在搜索单个字符时String.IndexOf(Char)方法与CompareInfo.IndexOf(String, Char)方法之间的差异。 当使用 en-US 区域性的约定时,在字符串“aerial”中能找到连字“æ”(U+00E6),但使用 da-DK 区域性的约定时或者执行按顺序比较时则找不到。

using System;
using System.Globalization;

public class Example17
{
   public static void Main()
   {
      String[] cultureNames = { "da-DK", "en-US" };
      CompareInfo ci;
      String str = "aerial";
      Char ch = 'æ';  // U+00E6
      
      Console.Write("Ordinal comparison -- ");
      Console.WriteLine($"Position of '{ch}' in {str}: {str.IndexOf(ch)}");
      
      foreach (var cultureName in cultureNames) {
         ci = CultureInfo.CreateSpecificCulture(cultureName).CompareInfo;
         Console.Write("{0} cultural comparison -- ", cultureName);
         Console.WriteLine($"Position of '{ch}' in {str}: {ci.IndexOf(str, ch)}");
      }
   }
}
// The example displays the following output:
//       Ordinal comparison -- Position of 'æ' in aerial: -1
//       da-DK cultural comparison -- Position of 'æ' in aerial: -1
//       en-US cultural comparison -- Position of 'æ' in aerial: 0
open System.Globalization

let cultureNames = [| "da-DK"; "en-US" |]
let str = "aerial"
let ch = 'æ'  // U+00E6

printf "Ordinal comparison -- "
printfn $"Position of '{ch}' in {str}: {str.IndexOf ch}"
                  
for cultureName in cultureNames do
    let ci = CultureInfo.CreateSpecificCulture(cultureName).CompareInfo
    printf $"{cultureName} cultural comparison -- "
    printfn $"Position of '{ch}' in {str}: {ci.IndexOf(str, ch)}"
// The example displays the following output:
//       Ordinal comparison -- Position of 'æ' in aerial: -1
//       da-DK cultural comparison -- Position of 'æ' in aerial: -1
//       en-US cultural comparison -- Position of 'æ' in aerial: 0
Imports System.Globalization

Module Example19
    Public Sub Main()
        Dim cultureNames() As String = {"da-DK", "en-US"}
        Dim ci As CompareInfo
        Dim str As String = "aerial"
        Dim ch As Char = "æ"c  ' U+00E6

        Console.Write("Ordinal comparison -- ")
        Console.WriteLine("Position of '{0}' in {1}: {2}", ch, str,
                        str.IndexOf(ch))

        For Each cultureName In cultureNames
            ci = CultureInfo.CreateSpecificCulture(cultureName).CompareInfo
            Console.Write("{0} cultural comparison -- ", cultureName)
            Console.WriteLine("Position of '{0}' in {1}: {2}", ch, str,
                           ci.IndexOf(str, ch))
        Next
    End Sub
End Module
' The example displays the following output:
'       Ordinal comparison -- Position of 'æ' in aerial: -1
'       da-DK cultural comparison -- Position of 'æ' in aerial: -1
'       en-US cultural comparison -- Position of 'æ' in aerial: 0

另一方面,如果没有通过 StringComparison 类型的参数显式指定搜索选项,那么 String 类中用于搜索字符串(而不是字符)的方法会执行区分区域性的搜索。 唯一的例外是 Contains执行序号搜索。

相等性测试

String.Compare使用该方法确定排序顺序中的两个字符串之间的关系。 通常,这是一个文化敏感的操作。 相比之下,调用 String.Equals 方法以测试相等性。 由于对相等性的测试通常将用户输入与一些已知字符串(例如有效的用户名、密码或文件系统路径)进行比较,因此它通常是序号操作。

警告

可以通过调用 String.Compare 方法并确定返回值是否为零来测试相等性。 但是,不建议使用这种做法。 若要确定两个字符串是否相等,应调用方法的 String.Equals 重载之一。 要调用的首选重载是实例 Equals(String, StringComparison) 方法或静态 Equals(String, String, StringComparison) 方法,因为这两种方法都包含显式指定比较类型的 System.StringComparison 参数。

下面的示例说明了本应使用按顺序比较来判断相等性,却使用了区分区域性的比较时所带来的风险。 在这种情况下,代码的目的是通过对 URL 开头和字符串 "FILE://" 执行不分大小写的比较,来禁止文件系统访问以 "FILE://" 或 "file://" 开头的 URL。 但是,如果在以“file://”开头的 URL 上使用土耳其(Turkey)文化执行文化敏感比较,则相等比较会失败,因为小写“i”的土耳其大写等效项是“İ”而不是“I”。 因此,无意之间允许了访问文件系统。 另一方面,如果执行按顺序比较,相等性比较会成功,并且会拒绝文件系统访问。

using System;
using System.Globalization;
using System.Threading;

public class Example4
{
   public static void Main()
   {
      Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("tr-TR");      

      string filePath = "file://c:/notes.txt";
      
      Console.WriteLine("Culture-sensitive test for equality:");
      if (! TestForEquality(filePath, StringComparison.CurrentCultureIgnoreCase))
         Console.WriteLine($"Access to {filePath} is allowed.");
      else
         Console.WriteLine($"Access to {filePath} is not allowed.");
      
      Console.WriteLine("\nOrdinal test for equality:");
      if (! TestForEquality(filePath, StringComparison.OrdinalIgnoreCase))
         Console.WriteLine($"Access to {filePath} is allowed.");
      else
         Console.WriteLine($"Access to {filePath} is not allowed.");
   }

   private static bool TestForEquality(string str, StringComparison cmp)
   {
      int position = str.IndexOf("://");
      if (position < 0) return false;

      string substring = str.Substring(0, position);  
      return substring.Equals("FILE", cmp);
   }
}
// The example displays the following output:
//       Culture-sensitive test for equality:
//       Access to file://c:/notes.txt is allowed.
//       
//       Ordinal test for equality:
//       Access to file://c:/notes.txt is not allowed.
open System
open System.Globalization
open System.Threading

let testForEquality (str: string) (cmp: StringComparison) =
    let position = str.IndexOf "://"
    if position < 0 then false
    else
        let substring = str.Substring(0, position)
        substring.Equals("FILE", cmp)

Thread.CurrentThread.CurrentCulture <- CultureInfo.CreateSpecificCulture "tr-TR"

let filePath = "file://c:/notes.txt"

printfn "Culture-sensitive test for equality:"
if not (testForEquality filePath StringComparison.CurrentCultureIgnoreCase) then
    printfn $"Access to {filePath} is allowed."
else
    printfn $"Access to {filePath} is not allowed."

printfn "\nOrdinal test for equality:"
if not (testForEquality filePath StringComparison.OrdinalIgnoreCase) then
    printfn $"Access to {filePath} is allowed."
else
    printfn $"Access to {filePath} is not allowed."

// The example displays the following output:
//       Culture-sensitive test for equality:
//       Access to file://c:/notes.txt is allowed.
//
//       Ordinal test for equality:
//       Access to file://c:/notes.txt is not allowed.
Imports System.Globalization
Imports System.Threading

Module Example7
    Public Sub Main()
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("tr-TR")

        Dim filePath As String = "file://c:/notes.txt"

        Console.WriteLine("Culture-sensitive test for equality:")
        If Not TestForEquality(filePath, StringComparison.CurrentCultureIgnoreCase) Then
            Console.WriteLine("Access to {0} is allowed.", filePath)
        Else
            Console.WriteLine("Access to {0} is not allowed.", filePath)
        End If
        Console.WriteLine()

        Console.WriteLine("Ordinal test for equality:")
        If Not TestForEquality(filePath, StringComparison.OrdinalIgnoreCase) Then
            Console.WriteLine("Access to {0} is allowed.", filePath)
        Else
            Console.WriteLine("Access to {0} is not allowed.", filePath)
        End If
    End Sub

    Private Function TestForEquality(str As String, cmp As StringComparison) As Boolean
        Dim position As Integer = str.IndexOf("://")
        If position < 0 Then Return False

        Dim substring As String = str.Substring(0, position)
        Return substring.Equals("FILE", cmp)
    End Function
End Module
' The example displays the following output:
'       Culture-sensitive test for equality:
'       Access to file://c:/notes.txt is allowed.
'       
'       Ordinal test for equality:
'       Access to file://c:/notes.txt is not allowed.

标准化

某些 Unicode 字符具有多个表示形式。 例如,以下任何代码点都可以表示字母“ắ”:

  • U+1EAF
  • U+0103 U+0301
  • U+0061 U+0306 U+0301

单个字符的多个表示形式使搜索、排序、匹配和其他字符串操作复杂化。

Unicode 标准定义一个名为规范化的进程,该进程为其任何等效二进制表示形式返回 Unicode 字符的一个二进制表示形式。 规范化可以使用遵循不同规则的多个算法(称为规范化形式)。 .NET 支持 Unicode 规范化形式 C、D、KC 和 KD。 将字符串规范化为同一规范化形式时,可以使用序号比较来比较字符串。

按顺序比较是对每个字符串中相应 Char 对象的 Unicode 标量值的二进制比较。 该 String 类包含许多可执行序号比较的方法,包括以下内容:

可以通过调用 String.IsNormalized() 方法来确定字符串是否规范化为规范化形式 C,也可以调用 String.IsNormalized(NormalizationForm) 该方法来确定字符串是否规范化为指定的规范化形式。 还可以调用 String.Normalize() 方法将字符串转换为规范化表单 C,也可以调用 String.Normalize(NormalizationForm) 该方法将字符串转换为指定的规范化窗体。 有关规范化和比较字符串的分步信息,请参阅 Normalize()Normalize(NormalizationForm) 方法。

下面的简单示例说明了字符串规范化。 它在三个不同的字符串中以三种不同的方式定义字母“ố”,并使用序号比较来确定每个字符串与其他两个字符串不同。 然后,它将每个字符串转换为受支持的规范化形式,并再次执行指定规范化形式的每个字符串的序号比较。 在每个情况下,第二个相等测试显示字符串相等。

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

public class Example13
{
   private static StreamWriter sw;
   
   public static void Main()
   {
      sw = new StreamWriter(@".\TestNorm1.txt");

      // Define three versions of the same word. 
      string s1 = "sống";        // create word with U+1ED1
      string s2 = "s\u00F4\u0301ng";
      string s3 = "so\u0302\u0301ng";

      TestForEquality(s1, s2, s3);      
      sw.WriteLine();

      // Normalize and compare strings using each normalization form.
      foreach (string formName in Enum.GetNames(typeof(NormalizationForm)))
      {
         sw.WriteLine("Normalization {0}:\n", formName); 
         NormalizationForm nf = (NormalizationForm) Enum.Parse(typeof(NormalizationForm), formName);
         string[] sn = NormalizeStrings(nf, s1, s2, s3);
         TestForEquality(sn);           
         sw.WriteLine("\n");                                        
      }
      
      sw.Close();   
   }

   private static void TestForEquality(params string[] words)
   {
      for (int ctr = 0; ctr <= words.Length - 2; ctr++)
         for (int ctr2 = ctr + 1; ctr2 <= words.Length - 1; ctr2++) 
            sw.WriteLine("{0} ({1}) = {2} ({3}): {4}", 
                         words[ctr], ShowBytes(words[ctr]),
                         words[ctr2], ShowBytes(words[ctr2]),
                         words[ctr].Equals(words[ctr2], StringComparison.Ordinal));
   }

   private static string ShowBytes(string str)
   {
      string result = null;
      foreach (var ch in str)
         result += $"{(ushort)ch:X4} ";
      return result.Trim();            
   } 
   
   private static string[] NormalizeStrings(NormalizationForm nf, params string[] words)
   {
      for (int ctr = 0; ctr < words.Length; ctr++)
         if (! words[ctr].IsNormalized(nf))
            words[ctr] = words[ctr].Normalize(nf); 
      return words;   
   }
}
// The example displays the following output:
//       sống (0073 1ED1 006E 0067) = sống (0073 00F4 0301 006E 0067): False
//       sống (0073 1ED1 006E 0067) = sống (0073 006F 0302 0301 006E 0067): False
//       sống (0073 00F4 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): False
//       
//       Normalization FormC:
//       
//       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
//       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
//       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
//       
//       
//       Normalization FormD:
//       
//       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
//       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
//       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
//       
//       
//       Normalization FormKC:
//       
//       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
//       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
//       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
//       
//       
//       Normalization FormKD:
//       
//       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
//       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
//       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
open System
open System.IO
open System.Text

do
    use sw = new StreamWriter(@".\TestNorm1.txt")

    let showBytes (str: string) =
        let mutable result = ""
        for ch in str do
            result <- result + $"{uint16 ch:X4} "
        result.Trim()
    
    let testForEquality (words: string[]) =
        for ctr = 0 to words.Length - 2 do
            for ctr2 = ctr + 1 to words.Length - 1 do
                sw.WriteLine("{0} ({1}) = {2} ({3}): {4}",
                            words[ctr], showBytes(words[ctr]),
                            words[ctr2], showBytes(words[ctr2]),
                            words[ctr].Equals(words[ctr2], StringComparison.Ordinal))

    let normalizeStrings nf (words: string[]) =
        for i = 0 to words.Length - 1 do
            if not (words[i].IsNormalized nf) then
                words[i] <- words[i].Normalize nf
        words

    // Define three versions of the same word.
    let s1 = "sống"        // create word with U+1ED1
    let s2 = "s\u00F4\u0301ng"
    let s3 = "so\u0302\u0301ng"

    testForEquality [| s1; s2; s3 |]
    sw.WriteLine()

    // Normalize and compare strings using each normalization form.
    for formName in Enum.GetNames typeof<NormalizationForm> do
        sw.WriteLine("Normalization {0}:\n", formName)
        let nf = Enum.Parse(typeof<NormalizationForm>, formName) :?> NormalizationForm
        let sn = normalizeStrings nf [| s1; s2; s3|]
        testForEquality sn
        sw.WriteLine "\n"

// The example displays the following output:
//       sống (0073 1ED1 006E 0067) = sống (0073 00F4 0301 006E 0067): False
//       sống (0073 1ED1 006E 0067) = sống (0073 006F 0302 0301 006E 0067): False
//       sống (0073 00F4 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): False
//
//       Normalization FormC:
//
//       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
//       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
//       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
//
//
//       Normalization FormD:
//
//       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
//       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
//       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
//
//
//       Normalization FormKC:
//
//       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
//       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
//       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
//
//
//       Normalization FormKD:
//
//       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
//       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
//       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
Imports System.Globalization
Imports System.IO
Imports System.Text

Module Example16
    Private sw As StreamWriter

    Public Sub Main()
        sw = New StreamWriter(".\TestNorm1.txt")

        ' Define three versions of the same word. 
        Dim s1 As String = "sống"        ' create word with U+1ED1
        Dim s2 As String = "s" + ChrW(&HF4) + ChrW(&H301) + "ng"
        Dim s3 As String = "so" + ChrW(&H302) + ChrW(&H301) + "ng"

        TestForEquality(s1, s2, s3)
        sw.WriteLine()

        ' Normalize and compare strings using each normalization form.
        For Each formName In [Enum].GetNames(GetType(NormalizationForm))
            sw.WriteLine("Normalization {0}:", formName)
            Dim nf As NormalizationForm = CType([Enum].Parse(GetType(NormalizationForm), formName),
                                             NormalizationForm)
            Dim sn() As String = NormalizeStrings(nf, s1, s2, s3)
            TestForEquality(sn)
            sw.WriteLine(vbCrLf)
        Next

        sw.Close()
    End Sub

    Private Sub TestForEquality(ParamArray words As String())
        For ctr As Integer = 0 To words.Length - 2
            For ctr2 As Integer = ctr + 1 To words.Length - 1
                sw.WriteLine("{0} ({1}) = {2} ({3}): {4}",
                         words(ctr), ShowBytes(words(ctr)),
                         words(ctr2), ShowBytes(words(ctr2)),
                         words(ctr).Equals(words(ctr2), StringComparison.Ordinal))
            Next
        Next
    End Sub

    Private Function ShowBytes(str As String) As String
        Dim result As String = Nothing
        For Each ch In str
            result += String.Format("{0} ", Convert.ToUInt16(ch).ToString("X4"))
        Next
        Return result.Trim()
    End Function

    Private Function NormalizeStrings(nf As NormalizationForm, ParamArray words() As String) As String()
        For ctr As Integer = 0 To words.Length - 1
            If Not words(ctr).IsNormalized(nf) Then
                words(ctr) = words(ctr).Normalize(nf)
            End If
        Next
        Return words
    End Function
End Module
' The example displays the following output:
'       sống (0073 1ED1 006E 0067) = sống (0073 00F4 0301 006E 0067): False
'       sống (0073 1ED1 006E 0067) = sống (0073 006F 0302 0301 006E 0067): False
'       sống (0073 00F4 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): False
'       
'       Normalization FormC:
'       
'       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
'       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
'       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
'       
'       
'       Normalization FormD:
'       
'       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
'       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
'       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
'       
'       
'       Normalization FormKC:
'       
'       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
'       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
'       sống (0073 1ED1 006E 0067) = sống (0073 1ED1 006E 0067): True
'       
'       
'       Normalization FormKD:
'       
'       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
'       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True
'       sống (0073 006F 0302 0301 006E 0067) = sống (0073 006F 0302 0301 006E 0067): True

有关规范化和规范化表单的详细信息,请参阅 System.Text.NormalizationFormunicode.org 网站上的 Unicode 标准附件 #15:Unicode 规范化窗体规范化常见问题解答

按类别划分的字符串操作

String 类提供用于比较字符串、测试字符串是否相等、查找字符串中的字符或子字符串、修改字符串、从字符串中提取子字符串、组合字符串、格式设置值、复制字符串以及规范化字符串的成员。

比较字符串

可以使用以下 String 方法比较字符串,以确定其按排序顺序的相对位置:

  • Compare 返回一个整数,指示一个字符串与排序顺序中的第二个字符串之间的关系。

  • CompareOrdinal 返回一个整数,该整数指示一个字符串与第二个字符串的关系,具体取决于其代码点的比较。

  • CompareTo 返回一个整数,指示当前字符串实例与排序顺序中的第二个字符串之间的关系。 CompareTo(String) 方法为 String 类提供 IComparableIComparable<T> 的实现。

测试字符串的相等性

Equals调用该方法以确定两个字符串是否相等。 通过实例 Equals(String, String, StringComparison) 和静态 Equals(String, StringComparison) 重载,你可以指定是按区域性比较还是按顺序比较,以及是否考虑大小写。 大多数相等性测试都是基于序数的, 用于确定对系统资源(例如文件系统对象)访问的相等性比较应始终基于序数。

在字符串中查找字符

String 类包括两种类型的搜索方法:

警告

如果要搜索特定模式而不是特定子字符串的字符串,则应使用正则表达式。 有关详细信息,请参阅 .NET 正则表达式

修改字符串

String 类包括以下似乎修改字符串值的方法:

  • Insert 将字符串插入当前 String 实例。

  • PadLeft 在字符串开头插入一个或多个指定字符。

  • PadRight 在字符串末尾插入一个或多个指定字符。

  • Remove 从当前 String 实例中删除子字符串。

  • Replace 将子字符串替换为当前 String 实例中的另一个子字符串。

  • ToLower 并将 ToLowerInvariant 字符串中的所有字符转换为小写。

  • ToUpper 并将 ToUpperInvariant 字符串中的所有字符转换为大写。

  • Trim 从字符串的开头和结尾删除指定字符的所有实例。

  • TrimEnd 从字符串末尾删除一个字符的所有出现。

  • TrimStart 从字符串开头删除所有出现的特定字符。

重要

所有字符串修改方法都返回一个新 String 对象。 它们不会修改当前实例的值。

从字符串中提取子字符串

该方法 String.Split 将单个字符串分隔为多个字符串。 该方法的重载允许指定多个分隔符、限制方法提取的子字符串数、从子字符串中剪裁空格,以及指定返回的字符串中是否包含空字符串(在分隔符相邻时发生)。

合并字符串

以下 String 方法可用于字符串串联:

  • Concat 将一个或多个子字符串合并为一个字符串。
  • Join 将一个或多个子字符串连接在一个元素中,并在每个子字符串之间添加一个分隔符。

设置值格式

该方法 String.Format 使用复合格式设置功能将字符串中的一个或多个占位符替换为某些对象或值的字符串表示形式。 该方法 Format 通常用于执行以下操作:

  • 将数值的字符串表示嵌入到字符串中。
  • 在字符串中嵌入日期和时间值的字符串表示形式。
  • 将枚举值的字符串表示形式嵌入到一个字符串中。
  • 将支持IFormattable接口的某些对象的字符串表示形式嵌入到字符串中。
  • 使一个较长字符串中某个字段内的子字符串右对齐或左对齐。

有关设置操作和示例的详细信息,请参阅 Format 重载摘要。

复制字符串

可以调用以下 String 方法来创建字符串的副本:

  • Clone 返回对现有 String 对象的引用。
  • CopyTo 将字符串的一部分复制到字符数组。

规范化字符串

在 Unicode 中,单个字符可以有多个码位。 规范化将这些等效字符转换为相同的二进制表示形式。 该方法 String.Normalize 执行规范化,该方法 String.IsNormalized 确定字符串是否规范化。

有关详细信息和示例,请参阅 本文前面的规范化 部分。