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# 中,由于反斜杠 (\) 是转义字符,字符串中的文本反斜杠必须转义,或者整个字符串必须为 @-quoted。

    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 创建单个字符串。 下面的示例演示了字符串串联运算符的使用。

    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 补充平面中的字符。

  • 图形由基字符表示,后跟一个或多个组合字符。 例如,字符 ä 由 Char 代码点为 U+0061 的对象表示,后跟 Char 代码点为 U+0308 的对象。 此字符也可以由具有 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: {0}", 
                      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 标准版本。

嵌入的 null 字符

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

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

  • strcpy_swcscpy_s 函数创建的字符串不一定与方法创建的 String.Copy 字符串相同。

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

在对字符串进行排序(或比较)和搜索字符串时,字符串中的嵌入 null 字符也会受到不同的处理。 在两个字符串之间执行区分区域性的比较时,将忽略 Null 字符,包括使用固定区域性的比较。 它们仅用于序号或不区分大小写的序号比较。 另一方面,使用诸如ContainsStartsWithIndexOf和等方法搜索字符串时,始终考虑嵌入的 null 字符。

字符串和索引

索引是对象(而不是 Unicode 字符)在一个String中的位置Char。 索引是从字符串中第一个位置开始的从零开始的非负数,即索引位置为零。 许多搜索方法(例如 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\n   {0}\nhas {1} words.",
                  s1, nWords);                                                                     
// 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接口,因此也可以使用构造循环访问Char字符串foreach中的对象,如以下示例所示。

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\n   {0}\nhas {1} words.",
                  s1, nWords);                                                                     
// 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 结构。 以下示例演示了使用对象的代码与使用 Unicode 字符的代码之间的差异 Char 。 它比较句子的每个单词中的字符数或文本元素数。 该字符串包含两个基字符序列,后跟组合字符。

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

以下示例在IsNullOrEmpty自定义Temperature类的实现中使用IFormattable.ToString该方法。 该方法支持“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 在上一个示例中使用的字符串,将区域中的 1000 个随机字符连接为0x0001以0x052F对象 StringBuilder

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)方法对两个字符串执行区分区域性的比较,而应使用参数的值StringComparison.CurrentCulturecomparisonType调用Compare(String, String, StringComparison)该方法。 有关详细信息,请参阅有关使用字符串的最佳实践

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

大小写

大小写规则确定如何更改 Unicode 字符的大小写;例如,从小写到大写。 通常,在字符串比较之前执行大小写操作。 例如,字符串可以转换为大写,以便可以将其与其他大写字符串进行比较。 可以通过调用ToLower或方法将字符串中的字符转换为小写,并且可以通过调用ToUpperToLowerInvariantToUpperInvariant方法将其转换为大写。 此外,还可以使用 TextInfo.ToTitleCase 该方法将字符串转换为标题大小写。

注意

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

大小写操作可以基于当前区域性、指定的区域性或固定区域性的规则。 由于大小写映射可能因使用的区域性而异,因此大小写操作的结果可能因区域性而异。 大小写的实际差异有三种:

  • 拉丁文大写字母 I(U+0049)、拉丁文小写字母 I(U+0069)、带上点的拉丁文大写字母 I(U+0130)和拉丁文小写字母 DOTLESS I(U+0131)的情况映射的差异。 在 tr-TR(土耳其(土耳其)和 az-Latn-AZ(阿塞拜疆、拉丁文)区域性中,在 tr、az 和 az-Latn 中性区域性中,拉丁文大写字母 I 是拉丁文小写字母 I 是拉丁文小写字母 DOTLESS 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: {0}", CultureInfo.CurrentCulture.DisplayName);
             Console.WriteLine(resource);
             Console.WriteLine("Access allowed: {0}", 
                               ! 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) 大写 GR企业版K 大写字母 MU (U+-39C)
    上面带点的拉丁文大写字母 I (U+0130) 小写 拉丁文小写字母 I (U+0069)
    拉丁文小写字母无点 I (U+0131) 大写 拉丁文大写字母 I (U+0049)
    拉丁文小写字母 LONG S (U+017F) 大写 拉丁文大写字母 S (U+0053)
    带带 CARON 的小写字母 Z 的拉丁文大写字母 D (U+01C5) 小写 带 CARON 的拉丁文小写字母 DZ (U+01C6)
    合并 GR企业版K YPOGEGRAMMENI (U+0345) 大写 GR企业版K 大写字母 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”、“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

分析和格式设置

格式设置和分析是逆运算。 格式规则确定如何将值(如日期和时间或数字)转换为其字符串表示形式,而分析规则确定如何将字符串表示形式转换为日期和时间等值。 格式设置和分析规则都依赖于文化约定。 以下示例说明了解释区域性特定的日期字符串时可能出现的歧义性。 如果不知道用于生成日期字符串的区域性约定,则无法知道 2011 年 3 月 1 日、2011 年 3 月 1 日和 2011 年 1 月 3 日和 01/03/03 表示 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具有提供值CompareOptions.StringSort的参数的方法重载,可以使用字符串排序规则来比较两个options字符串。 请注意,这是 .NET 提供的唯一方法,用于使用字符串排序规则比较两个字符串。

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

区分区域性的比较是显式或隐式使用 CultureInfo 对象的任何比较,包括属性指定的 CultureInfo.InvariantCulture 固定区域性。 隐式区域性是当前区域性,由 Thread.CurrentCulture 属性 CultureInfo.CurrentCulture 指定。 字母字符的排序顺序(即属性返回trueChar.IsLetter字符)在区域性中存在相当大的变化。 可以通过向字符串比较方法(例如Compare(String, String, CultureInfo, CompareOptions))提供CultureInfo对象来指定使用特定区域性约定的区分区域性的比较。 可以通过提供StringComparison.CurrentCultureStringComparison.CurrentCultureIgnoreCase或提供方法的相应重载以外的CompareOptions.OrdinalIgnoreCaseCompareOptions.Ordinal枚举的任何成员CompareOptions来指定使用当前区域性约定的区分区域性的Compare比较。 区分区域性的比较通常适用于排序,而序号比较则不适用。 序号比较通常适用于确定两个字符串是否相等(即确定标识),而区分区域性的比较则不适用。

以下示例说明了区分区域性的比较和序号比较之间的差异。 该示例使用序号比较和 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: {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}\n", 
                        str2, str3, String.Compare(str2, str3));
      
      // 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}\n", 
                        str2, str3, String.Compare(str2, str3));
      
      // 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));
   }
}
// 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("[{0}]: {1}", 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("'{0}' found in {1} at position {2}", 
                           substring, s, result);
      else
         Console.WriteLine("'{0}' not found in {1}", 
                           substring, 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),具体取决于区域性。 以下示例说明了在搜索单个字符时与CompareInfo.IndexOf(String, Char)方法之间的差异String.IndexOf(Char)。 使用 en-US 区域性的约定,但在使用 da-DK 区域性约定或执行序号比较时,在字符串“空中”中找到连字“æ”(U+00E6)。

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 '{0}' in {1}: {2}", ch, str,
                        str.IndexOf(ch));
      
      foreach (var 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));
      }
   }
}
// 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

另一方面, String 搜索字符串而不是字符的类方法执行区分区域性的搜索(如果搜索选项不是由类型的 StringComparison参数显式指定)。 唯一的例外是 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 上使用土耳其(土耳其)区域性执行区分区域性的比较,则相等比较会失败,因为小写“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 {0} is allowed.", filePath);
      else
         Console.WriteLine("Access to {0} is not allowed.", filePath);
      
      Console.WriteLine("\nOrdinal test for equality:");
      if (! TestForEquality(filePath, StringComparison.OrdinalIgnoreCase))
         Console.WriteLine("Access to {0} is allowed.", filePath);
      else
         Console.WriteLine("Access to {0} is not allowed.", filePath);
   }

   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) 为类提供 IComparableIComparable<T> 实现 String

测试字符串的相等性

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 对象的引用。
  • Copy 创建现有字符串的副本。
  • CopyTo 将字符串的一部分复制到字符数组。

规范化字符串

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

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