Класс System.String

В этой статье приводятся дополнительные замечания к справочной документации по этому API.

Строка — это последовательная коллекция символов, используемых для представления текста. String Объект представляет собой последовательную коллекцию System.Char объектов, представляющих строку; System.Char объект соответствует единице кода UTF-16. Значение String объекта — это содержимое последовательной коллекции System.Char объектов, и это значение является неизменяемым (т. е. только для чтения). Дополнительные сведения о неизменяемости строк см . в разделе "Неизменяемость" и "Класс StringBuilder". Максимальный String размер объекта в памяти составляет 2 ГБ или около 1 миллиарда символов.

Дополнительные сведения об Юникоде, UTF-16, единицах кода, точках кода и CharRune типах см. в статье "Введение в кодировку символов в .NET".

Создание экземпляра объекта String

Создать экземпляр String объекта можно следующим образом:

  • Назначая строковый литерал переменной String . Это наиболее часто используемый метод для создания строки. В следующем примере используется назначение для создания нескольких строк. Обратите внимание, что в C# и F#, так как обратная косая черта (\) является escape-символом, литеральные обратные косые черты в строке должны быть экранированы или вся строка должна быть @-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 и символы Юникода

Каждый символ в строке определяется скалярным значением Юникода, также называемым точкой кода Юникода или порядковым (числовым) значением символа Юникода. Каждая точка кода кодируется с помощью кодировки UTF-16, а числовое значение каждого элемента кодирования представлено Char объектом.

Примечание.

Обратите внимание, что, поскольку String экземпляр состоит из последовательной коллекции единиц кода UTF-16, можно создать String объект, который не является хорошо сформированной строкой Юникода. Например, можно создать строку с низким суррогатом без соответствующего высокого суррогата. Хотя некоторые методы, такие как методы кодирования и декодирования объектов в System.Text пространстве имен, могут выполнять проверка, чтобы гарантировать, что строки хорошо сформированы, String члены класса не гарантируют, что строка хорошо сформирована.

Один Char объект обычно представляет одну кодовую точку, то есть числовое значение Char точки кода равно кодовой точке. Например, кодовая точка для символа "a" — U+0061. Однако для точки кода может потребоваться несколько закодированных элементов (более одного Char объекта). Стандарт Юникода определяет два типа символов, которые соответствуют нескольким Char объектам: graphemes и Дополнительным точкам кода Юникода, соответствующим символам в дополнительных плоскостях Юникода.

  • Графем представлен базовым символом, за которым следует один или несколько объединенных символов. Например, символ ä представлен Char объектом, кодовая точка которого — U+0061, за которой следует Char объект, кодовая точка которого — U+0308. Этот символ также можно определить одним Char объектом, который имеет кодовую точку U+00E4. Как показано в следующем примере, сравнение с учетом языка и региональных параметров для равенства указывает, что эти два представления равны, хотя обычное порядковое сравнение не выполняется. Однако если две строки нормализуются, порядковое сравнение также указывает, что они равны. (Дополнительные сведения об нормализации строк см. в разделеРаздел нормализации .)

    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
    
  • Дополнительная кодовая точка Юникода (суррогатная пара) представлена Char объектом, кодовая точка которого является высокой суррогатной, за которой следует Char объект, кодовая точка которого является низкой суррогатной. Единицы кода с высоким уровнем суррогатов варьируются от U+D800 до U+DBFF. Единицы кода низких суррогатов варьируются от U+DC00 до U+DFFF. Суррогатные пары используются для представления символов в 16 дополнительных плоскостях Юникода. В следующем примере создается суррогатный символ и передается 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
    

Стандарт Юникода

Символы в строке представлены единицами кода в кодировке UTF-16, которые соответствуют Char значениям.

Каждый символ в строке имеет связанную категорию символов Юникода, которая представлена в .NET UnicodeCategory перечислением. Категорию символа или суррогатной пары можно определить путем вызова CharUnicodeInfo.GetUnicodeCategory метода.

.NET поддерживает собственную таблицу символов с соответствующими категориями, которая гарантирует, что конкретные реализации .NET будут возвращать одни и те же данные по категориям символов на разных платформах. Во всех версиях .NET и на всех платформах ОС сведения о категории символов предоставляются базой данных символов Юникода.

В следующей таблице перечислены версии .NET и версии стандарта Юникод, на котором основаны их категории символов.

Версия .NET Версия стандарта Юникод
.NET Framework 1.1 Стандарт Юникод, версия 4.0.0
.NET Framework 2.0 Стандарт Юникод, версия 5.0.0
.NET Framework 3.5 Стандарт Юникод, версия 5.0.0
.NET Framework 4 Стандарт Юникод, версия 5.0.0
.NET Framework 4.5 Стандарт Юникод, версия 6.3.0
.NET Framework 4.5.1 Стандарт Юникод, версия 6.3.0
.NET Framework 4.5.2 Стандарт Юникод, версия 6.3.0
.NET Framework 4.6 Стандарт Юникод, версия 6.3.0
.NET Framework 4.6.1 Стандарт Юникод, версия 6.3.0
.NET Framework 4.6.2 и более поздние версии Стандарт Юникод, версия 8.0.0
.NET Core 2.1 Стандарт Юникод, версия 8.0.0
.NET Core 3.1. Стандарт Юникода версии 11.0.0
.NET 5 Стандарт Юникода версии 13.0.0

Кроме того, .NET поддерживает сравнение строк и сортировку на основе стандарта Юникода. Начиная с платформа .NET Framework 4.5, работающей в Windows 8 и более поздних версиях операционной системы Windows, среда выполнения делегирует операции сравнения строк и сортировки в операционную систему. В .NET Core и .NET 5+ сведения о сравнении строк и сортировке предоставляются международными компонентами библиотек Юникода (за исключением версий Windows до обновление Windows 10 за май 2019 г.). В следующей таблице перечислены версии .NET и версии Юникода Standard, на основе которых основаны сравнение символов и сортировка.

Версия .NET Версия стандарта Юникод
.NET Framework 4.5 и более поздних версий в Windows 7 Стандарт Юникод, версия 5.0.0
платформа .NET Framework 4.5 и более поздних версий в операционных системах Windows 8 и более поздних версий Стандарт Юникод, версия 6.3.0
.NET Core и .NET 5 или более поздней версии Зависит от версии стандарта Юникода, поддерживаемой базовой операционной системой.

Внедренные пустые символы

В .NET String объект может включать внедренные пустые символы, которые считаются частью длины строки. Однако на некоторых языках, таких как C и C++, символ NULL указывает конец строки; он не считается частью строки и не считается частью длины строки. Это означает, что следующие распространенные предположения, что программисты или библиотеки C++, написанные в C или C++, могут не обязательно применяться к String объектам:

  • Значение, возвращаемое функцией или wcslen функциямиstrlen, не обязательно равноString.Length.

  • Строка, созданная методом strcpy_s , wcscpy_s не обязательно идентична строке, созданной методом String.Copy .

Необходимо убедиться, что собственный код C и C++, который создает экземпляры объектов, и код, передаваемый String через вызов платформы, не предполагает, что внедренный пустой символ помечает String конец строки.

Внедренные символы NULL в строке также обрабатываются по-разному при сортировке строки (или сравнении) и при поиске строки. Пустые символы игнорируются при выполнении сравнения с учетом языка и региональных параметров между двумя строками, включая сравнения с использованием инвариантного языка и региональных параметров. Они считаются только для порядковых или нечувствительных порядковых сравнений. С другой стороны, внедренные символы NULL всегда учитываются при поиске строки с помощью таких методов, как Contains, StartsWithи IndexOf.

Строки и индексы

Индекс — это позиция Char объекта (а не символа Юникода) в объекте String. Индекс — это отсчитываемое от нуля число, начинающееся с первой позиции в строке, которая является нулевой позицией индекса. Ряд методов поиска, таких как IndexOf и LastIndexOf, возвращает индекс символа или подстроки в экземпляре строки.

Свойство Chars[] позволяет получить доступ к отдельным Char объектам по их позиции индекса в строке. Chars[] Так как свойство является свойством по умолчанию (в Visual Basic) или индексатором (в C# и F#), вы можете получить доступ к отдельным Char объектам в строке с помощью кода, например следующего. Этот код ищет пробелы или знаки препинания в строке, чтобы определить, сколько слов содержит строка.

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

s1 = s1.Trim();      
for (int ctr = 0; ctr < s1.Length; ctr++) {
   if (Char.IsPunctuation(s1[ctr]) | Char.IsWhiteSpace(s1[ctr]))
      nWords++;              
}
Console.WriteLine("The sentence\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.

Последовательные значения индекса могут не соответствовать последовательными символами Юникода, так как символ Юникода может быть закодирован как несколько Char объектов. В частности, строка может содержать многозначные единицы текста, которые формируются базовым символом, за которым следует один или несколько объединенных символов или суррогатных пар. Для работы с символами Юникода Char вместо объектов используйте System.Globalization.StringInfo метод и TextElementEnumerator классы, а String.EnumerateRunes также метод и структуру Rune . В следующем примере показано различие между кодом, который работает с объектами и кодом, работающими с 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. В некоторых случаях передача пустой строки или пустой строки в качестве аргумента в вызове метода вызывает исключение. Например, передача пустой строки Int32.Parse методу вызывает ArgumentNullExceptionисключение, и передача пустой строки вызывает FormatExceptionисключение. В других случаях аргумент метода может быть пустой строкой или пустой строкой. Например, если вы предоставляете 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 в IFormattable.ToString реализации пользовательского Temperature класса. Метод поддерживает строки формата "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 используемые в предыдущем примере символы для объединения случайных символов в диапазоне на 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) вызова метода для сравнения двух строк с учетом языка и региональных параметров следует вызвать Compare(String, String, StringComparison) метод со значением StringComparison.CurrentCulture аргумента comparisonType . Дополнительные сведения см. в разделе Рекомендации по использованию строк.

Таблицы веса сортировки можно скачать набор текстовых файлов, содержащих сведения о весах символов, используемых в операциях сортировки и сравнения, со следующими ссылками:

Регистр

Правила регистра определяют, как изменить заглавную букву символа Юникода; например, из нижнего регистра в верхний регистр. Часто операция регистра выполняется перед сравнением строк. Например, строку можно преобразовать в верхний регистр, чтобы ее можно было сравнить с другой строкой верхнего регистра. Символы в строке можно преобразовать в строчный регистр, ToLowerToLowerInvariant вызвав или метод, и их можно преобразовать в верхний ToUpper регистр, вызвав метод или ToUpperInvariant метод. Кроме того, можно использовать TextInfo.ToTitleCase метод для преобразования строки в регистр заголовка.

Примечание.

.NET Core, работающей только в системах Linux и macOS: поведение сортировки для региональных параметров C и Posix всегда учитывает регистр, так как эти региональные параметры не используют ожидаемый порядок сортировки Юникода. Мы не рекомендуем использовать язык и региональные параметры, выбранные для C или Posix, для выполнения операций сортировки с учетом языка и региональных параметров, но без учета регистра.

Операции с регистром могут быть основаны на правилах текущего языка и региональных параметров, заданного языка и региональных параметров или инвариантного языка и региональных параметров. Так как сопоставления регистров могут отличаться в зависимости от используемого языка и региональных параметров, результат операций регистра может отличаться в зависимости от языка и региональных параметров. Фактические различия в регистре имеют три типа:

  • Различия в сопоставлении БУКВ ЛАТИНСКОЙ БУКВЫ I (U+0049), ЛАТИНСКАЯ МАЛЕНЬКАЯ БУКВА I (U+0069), ЛАТИНСКАЯ БУКВА С ТОЧКОЙ ВЫШЕ (U+0130) и ЛАТИНСКАЯ МАЛЕНЬКАЯ БУКВА DOTLESS I (U+0131). В tr-TR (турецкая (Турция)) и az-Latn-AZ (Азербайджан, латинская) культуры, а также в tr, az, и az-Latn нейтральных культур, строчные эквиваленты LATIN CAPITAL LETTER I — LATIN SMALL LETTER DOTLESS I, а верхний регистр — ЛАТИНСКАЯ БУКВА SMALL LETTER 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
    
  • Различия в сопоставлениях вариантов между инвариантной культурой и всеми другими языками и региональными параметрами. В таких случаях использование правил регистра инвариантного языка и региональных параметров для изменения символа на верхний или нижний регистр возвращает тот же символ. Для всех других языков и региональных параметров он возвращает другой символ. Некоторые затронутые символы перечислены в следующей таблице.

    Символ Если изменено на Возвраты
    ЗНАК MICRON (U+00B5) Верхний регистр ГРЕЧЕСКИЙ ПРОПИСНОЙ БУКВЫ MU (U+-39C)
    ЛАТИНСКАЯ БУКВА I С ТОЧКОЙ ВЫШЕ (U+0130) Нижний регистр ЛАТИНСКАЯ МАЛЕНЬКАЯ БУКВА I (U+0069)
    ЛАТИНСКАЯ МАЛЕНЬКАЯ БУКВА ТОЧКА I (U+0131) Верхний регистр ЛАТИНСКАЯ БУКВА I (U+0049)
    ЛАТИНСКАЯ МАЛЕНЬКАЯ БУКВА LONG S (U+017F) Верхний регистр ЛАТИНСКАЯ БУКВА (U+0053)
    ЛАТИНСКАЯ БУКВА D С НЕБОЛЬШОЙ БУКВОЙ Z С КАРОН (U+01C5) Нижний регистр ЛАТИНСКАЯ МАЛЕНЬКАЯ БУКВА DZ С CARON (U+01C6)
    CO МБ INING ГРЕЧЕСКИЙ YPOGEGRAMMENI (U+0345) Верхний регистр ГРЕЧЕСКИЙ ПРОПИСНОЙ БУКВЫ ИОТА (U+0399)
  • Различия в сопоставлениях двухбуквленных пар смешанного регистра в диапазоне символов ASCII. В большинстве языков и региональных параметров пара двухбуквенный смешанный регистр равна эквивалентной двухбуквеной или нижней паре. Это не верно для следующих двухбуквленных пар в следующих языках и региональных параметрах, так как в каждом случае они сравниваются с диграфом:

    • "lJ" и "nJ" в культуре hr-HR (Хорватская (Хорватия)).
    • "cH" в cs-CZ (Чехия)) и sk-SK (словацкая (Словакия)) культуры.
    • "aA" в культуре da-DK (Датская (Дания)).
    • "cS", "dZ", "dZS", "nY", "sZ", "tY" и "zS" в культуре hu-HU (Венгерская (Венгрия)).
    • "cH" и "lL" в культуре es-ES_tradnl (испанский (Испания, традиционная сортировка)).
    • "cH", "gI", "kH", "nG" "nH", "pH", "qU', "tH" и "tR" в культуре vi-VN (Вьетнам)).

    Однако нестандартно столкнуться с ситуацией, в которой сравнение этих пар с учетом языка и региональных параметров создает проблемы, так как эти пары являются редкими в фиксированных строках или идентификаторах.

В следующем примере показаны некоторые различия в правилах регистра между языками и региональными параметрами при преобразовании строк в верхний регистр.

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

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

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

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

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

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

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

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

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

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

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

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

Анализ и форматирование

Форматирование и синтаксический анализ являются обратными операциями. Правила форматирования определяют, как преобразовать значение, например дату и время или число, в его строковое представление, в то время как правила синтаксического анализа определяют, как преобразовать строковое представление в значение, например дату и время. Правила форматирования и анализа зависят от культурных соглашений. В следующем примере показана неоднозначность, которая может возникнуть при интерпретации строки даты, определенной языком и региональными параметрами. Без знания соглашений о языках и региональных параметрах, которые использовались для создания строки дат, невозможно знать, представляет ли 03.01.2011, 3.1.2011 и 01.03.2011 г. 3 января 2011 г. или 1 марта 2011 г.

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" в алфавите. Кроме того, сравнения могут быть чувствительными к регистру или нечувствительными к регистру, а правила регистра могут отличаться языком и региональными параметрами. С другой стороны, порядковое сравнение использует кодовые точки Юникода отдельных символов в строке при сравнении и сортировке строк.

Правила сортировки определяют алфавитный порядок символов Юникода и как две строки сравниваются друг с другом. Например, String.Compare(String, String, StringComparison) метод сравнивает две строки на StringComparison основе параметра. Если значение параметра равно StringComparison.CurrentCulture, метод выполняет лингвистическое сравнение, которое использует соглашения текущего языка и региональных параметров; если значение параметра равно StringComparison.Ordinal, метод выполняет порядковое сравнение. Следовательно, как показано в следующем примере, если текущий язык и региональные параметры — английский язык США, первый вызов метода (с учетом языка и региональных параметров) учитывает "a" меньше "A", но второй вызов String.Compare(String, String, StringComparison) того же метода (с использованием порядкового сравнения) считает "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 поддерживает правила сортировки слов, строк и порядковых номеров:

  • Сортировка слов выполняет сравнение строк с учетом языка и региональных параметров, в которых некоторые нефанумерные символы Юникода могут иметь специальные весы, назначенные им. Например, дефис (-) может иметь очень небольшой вес, назначенный ему, чтобы "coop" и "co-op" отображались рядом друг с другом в отсортированного списка. Список String методов, которые сравнивают две строки с помощью правил сортировки слов, см . в разделе "Строки" по разделу категории .

  • Сортировка строк также выполняет сравнение с учетом языка и региональных параметров. Это похоже на сортировку слов, за исключением того, что нет особых случаев, и все неэлементные символы приходят до всех буквенно-цифровых символов Юникода. Две строки можно сравнить с помощью правил сортировки строк, вызвав CompareInfo.Compare перегрузки метода, имеющие options параметр, предоставленный значением CompareOptions.StringSort. Обратите внимание, что это единственный метод, который предоставляет .NET для сравнения двух строк с помощью правил сортировки строк.

  • Порядковый сорт сравнивает строки на основе числового значения каждого Char объекта в строке. Порядковое сравнение автоматически учитывает регистр, так как строчные и верхние версии символа имеют разные кодовые точки. Однако если регистр не важен, можно указать порядковое сравнение, которое игнорирует случай. Это эквивалентно преобразованию строки в верхний регистр с помощью инвариантного языка и региональных параметров, а затем выполнения порядкового сравнения результатов. Список String методов, которые сравнивают две строки с использованием правил порядковой сортировки, см. в разделе "Строки" по категориям .

Сравнение с учетом языка и региональных параметров — это любое сравнение, которое явно или неявно использует CultureInfo объект, включая инвариантный язык и региональные параметры, указанные свойством CultureInfo.InvariantCulture . Неявный язык и региональные параметры — это текущий язык и региональные параметры, заданные свойствамиThread.CurrentCulture.CultureInfo.CurrentCulture Существует значительное изменение в порядке сортировки алфавитных символов (т. е. символов, для которых Char.IsLetter свойство возвращается true) в разных языках и региональных параметров. Можно указать сравнение с учетом языка и региональных параметров, которое использует соглашения определенного языка и региональных параметров, указав CultureInfo объект методу сравнения строк, например Compare(String, String, CultureInfo, CompareOptions). Можно указать сравнение с учетом языка и региональных параметров, которое использует соглашения текущего языка и региональных параметров, указав StringComparison.CurrentCultureStringComparison.CurrentCultureIgnoreCaseили любой член CompareOptions перечисления, отличный CompareOptions.Ordinal от или CompareOptions.OrdinalIgnoreCase соответствующего перегрузки Compare метода. Сравнение с учетом языка и региональных параметров обычно подходит для сортировки, в то время как порядковое сравнение не является. Порядковое сравнение обычно подходит для определения того, равны ли две строки (т. е. для определения удостоверения), а сравнение с учетом языка и региональных параметров не является.

В следующем примере показано различие между языком и порядком сравнения с учетом языка и региональных параметров. В примере вычисляются три строки: Apple, Æble и AEble, с использованием порядкового сравнения и соглашений о языках и региональных параметров da-DK и en-US (каждый из которых является языком и региональных параметров по умолчанию во время Compare вызова метода). Поскольку датский язык рассматривает символ "Æ" как отдельную букву и сортирует его после "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 сравнение строк зависит от версии Юникода Standard, поддерживаемой базовой операционной системой. В платформа .NET Framework версии 4.5 и более поздних версий, работающих в Windows 8 или более поздних версиях, сортировка, регистрирование, нормализация и символьная информация Юникода соответствуют стандарту Юникода 6.0. В других операционных системах Windows они соответствуют стандарту Юникода 5.0.

Дополнительные сведения о правилах сортировки слов, строк и порядковых порядков см. в System.Globalization.CompareOptions разделе. Дополнительные рекомендации по использованию каждого правила см. в рекомендациях по использованию строк.

Обычно методы Compare сравнения строк не вызываются напрямую, чтобы определить порядок сортировки строк. Вместо этого методы сравнения вызываются методами сортировки, такими как Array.Sort или List<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.StartsWith и String.IndexOf, также, могут выполнять сравнения строк с учетом языка и региональных параметров или порядковых строк. В следующем примере показаны различия между порядковые и региональные сравнения с учетом языка и региональных параметров с помощью IndexOf метода. Поиск с учетом языка и региональных параметров, в котором текущий язык и региональные параметры — английский (США) рассматривает подстроку "oe" для сопоставления лигатуры "json". Так как мягкий дефис (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.StartsWith и String.IndexOf, также могут выполнять сравнения строк с учетом языка и региональных параметров, чтобы определить, найден ли символ или подстрока в указанной строке.

Методы поиска в String классе, которые ищут отдельный символ, например IndexOf метод, или один из наборов символов, например IndexOfAny метод, все выполняют порядковый поиск. Чтобы выполнить поиск символа с учетом CompareInfo языка и региональных параметров, необходимо вызвать такой метод, как CompareInfo.IndexOf(String, Char) или CompareInfo.LastIndexOf(String, Char). Обратите внимание, что результаты поиска символа с использованием порядкового и культурного сравнения могут отличаться. Например, поиск предварительно компилированного символа Юникода, например лигатуры "Æ" (U+00C6), может соответствовать любому вхождлению его компонентов в правильной последовательности, например "AE" (U+041U+0045), в зависимости от языка и региональных параметров. В следующем примере показано различие между String.IndexOf(Char) методами и CompareInfo.IndexOf(String, Char) методами при поиске отдельного символа. Лигатура "æ" (U+00E6) найдена в строке "воздушный" при использовании соглашений языка и региональных параметров en-US, но не при использовании соглашений о культуре da-DK или при выполнении порядкового сравнения.

using System;
using System.Globalization;

public class Example17
{
   public static void Main()
   {
      String[] cultureNames = { "da-DK", "en-US" };
      CompareInfo ci;
      String str = "aerial";
      Char ch = 'æ';  // U+00E6
      
      Console.Write("Ordinal comparison -- ");
      Console.WriteLine("Position of '{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://", путем сравнения без учета регистра начала URL-адреса со строкой "FILE://". Однако если сравнение с учетом языка и региональных параметров выполняется с использованием турецкого языка (Турция) на URL-адресе, начинающемся с "file://", сравнение равенства завершается ошибкой, так как турецкий верхний регистр эквивалента нижнего регистра "i" имеет значение "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.

нормализация

Некоторые символы Юникода имеют несколько представлений. Например, любой из следующих кодовых точек может представлять букву ắ:

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

Несколько представлений для одного символа усложняют поиск, сортировку, сопоставление и другие строковые операции.

Стандарт Юникода определяет процесс, называемый нормализацией, который возвращает одно двоичное представление символа Юникода для любого из его эквивалентных двоичных представлений. Нормализация может использовать несколько алгоритмов, называемых формами нормализации, которые соответствуют разным правилам. .NET поддерживает формы нормализации Юникода C, D, KC и KD. Если строки нормализованы в той же форме нормализации, их можно сравнить с помощью порядкового сравнения.

Порядковое сравнение — это двоичное сравнение скалярного значения Юникода соответствующих Char объектов в каждой строке. Класс String включает ряд методов, которые могут выполнять порядковое сравнение, в том числе следующие:

Вы можете определить, нормализуется ли строка для нормализации формы C путем вызова String.IsNormalized() метода или вызвать 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.NormalizationForm. на веб-сайте unicode.org в приложении Юникода "Стандартный" #15: Формы нормализации Юникода и часто задаваемые вопросы о нормализации на веб-сайте unicode.org.

Строковые операции по категориям

Класс String предоставляет элементы для сравнения строк, тестирования строк для равенства, поиска символов или подстроок в строке, изменения строки, извлечения подстроок из строки, объединения строк, форматирования значений, копирования строки и нормализации строки.

Сравнение строк

Строки можно сравнить, чтобы определить их относительную позицию в порядке сортировки с помощью следующих String методов:

  • Compare возвращает целое число, указывающее связь одной строки со второй строкой в порядке сортировки.

  • CompareOrdinal возвращает целое число, указывающее связь одной строки со второй строкой на основе сравнения точек кода.

  • CompareTo возвращает целое число, указывающее связь текущего экземпляра строки со второй строкой в порядке сортировки. Этот CompareTo(String) метод предоставляет IComparable и IComparable<T> реализации для String класса.

Проверка строк на равенство

Вызывается Equals метод, чтобы определить, равны ли две строки. Экземпляр Equals(String, String, StringComparison) и статические Equals(String, StringComparison) перегрузки позволяют указать, учитывается ли сравнение с учетом языка и региональных параметров или порядковый номер, а также учитывается ли случай или игнорируется. Большинство тестов на равенство являются порядковые и сравнения для равенства, определяющих доступ к системным ресурсу (например, объекту файловой системы), всегда должны быть порядковые номера.

Поиск символов в строке

Класс String включает два типа методов поиска:

  • Методы, возвращающие значение, указывающие, присутствует ли определенная Boolean подстрока в строковом экземпляре. К ним относятся Containsметоды и EndsWithStartsWith методы.

  • Методы, указывающие начальную позицию подстроки в строковом экземпляре. К ним относятся IndexOfметоды , IndexOfAnyLastIndexOfи LastIndexOfAny методы.

Предупреждение

Если вы хотите выполнить поиск строки для определенного шаблона, а не определенной подстроки, следует использовать регулярные выражения. Дополнительные сведения см. в разделе регулярных выражений .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 копирует часть строки в массив символов.

Нормализация строки

В Юникоде один символ может иметь несколько точек кода. Нормализация преобразует эти эквивалентные символы в то же двоичное представление. Метод String.Normalize выполняет нормализацию, а String.IsNormalized метод определяет, нормализуется ли строка.

Дополнительные сведения и пример см. в разделе нормализации, приведенном ранее в этой статье.