Поделиться через


Рекомендации по использованию строк в .NET Framework

Платформа .NET Framework предоставляет расширенную поддержку разработки локализованных и глобализованных приложений, а также упрощает применение правил текущего или указанного языка и региональных параметров при выполнении таких распространенных операций, как сортировка строк и их отображение. Но операция по сортировке или отображению строк не всегда зависит от языка и региональных параметров. Например, строки, предназначенные для внутреннего использования приложением, обычно обрабатываются одинаково для всех языков и региональных параметров. Если независимые от языка и региональных параметров строковые данные, такие как XML-теги, HTML-теги, имена пользователей, пути файлов и имена системных объектов, интерпретируются как зависимые от языка и региональных параметров, то это может привести к трудно выявляемым ошибкам кода приложения, уменьшению производительности и в некоторых случаях к проблемам с безопасностью.

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

В этом разделе содержатся следующие подразделы.

  • Рекомендации по использованию строк

  • Явное задание сравнений строк

  • Подробные сведения по сравнению строк

  • Выбор элемента StringComparison для вызова метода

  • Наиболее распространенные методы сравнения строк в .NET Framework

  • Методы неявного сравнения строк

Рекомендации по использованию строк

При разработке на платформе .NET Framework необходимо следовать приведенным далее рекомендациям по использованию строк.

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

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

  • Для повышения производительности следует использовать сравнения с StringComparison.Ordinal или StringComparison.OrdinalIgnoreCase.

  • При отображении выходных результатов для пользователя следует применять операции со строками на основе StringComparison.CurrentCulture.

  • Для сравнения не лингвистических данных (например символьных) следует использовать не лингвистические значения StringComparison.Ordinal или StringComparison.OrdinalIgnoreCase вместо операций со строками на основе CultureInfo.InvariantCulture.

  • При нормализации строк для сравнения следует использовать метод String.ToUpperInvariant вместо метода String.ToLowerInvariant.

  • Для проверки, являются ли две строки равными, следует использовать перегрузку метода String.Equals.

  • Методы Compare и CompareTo следует использовать для сортировки строк, а не для проверки на равенство.

При использовании строк следует избегать применения приведенных далее методик.

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

  • В большинстве случаев не следует использовать операции со строками на основе StringComparison.InvariantCulture. Одно из немногих исключений – случай, когда сохраняются лингвистически значимые, но независимые от языка и региональных параметров данные.

  • Не следует использовать перегрузку метода String.Compare или CompareTo и проверку, равно ли возвращаемое значение 0, для определения равенства двух строк.

К началу

Явное задание сравнений строк

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

Элемент StringComparison

Описание

CurrentCulture

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

CurrentCultureIgnoreCase

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

InvariantCulture

Выполняет сравнение с учетом регистра и с использованием инвариантного языка и региональных параметров.

InvariantCultureIgnoreCase

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

Ordinal

Выполняет порядковое сравнение.

OrdinalIgnoreCase

Выполняет порядковое сравнение без учета регистра.

Например, метод IndexOf, возвращающий индекс части строки в объекте String, соответствующей символу или строке, имеет девять перегрузок:

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

  • Некоторые перегрузки с параметрами по умолчанию (те, которые выполняют поиск Char в экземпляре строки) выполняют порядковое сравнение, в то время как другие (те, которые ищут строку в экземпляре строки) учитывают язык и региональные параметры. Поскольку трудно запомнить, какой метод использует какие значения по умолчанию, легко перепутать перегрузки.

  • Назначение кода, зависящего от значений по умолчанию для вызовов метода, не ясно. В следующем примере, зависящем от значений по умолчанию, трудно понять, что именно планировал разработчик – порядковое или лингвистическое сравнение двух строк, и может ли разный регистр protocol и "http" привести к тому, что проверка на равенство возвратит значение false.

    Dim protocol As String = GetProtocol(url)       
    If String.Equals(protocol, "http") Then
       ' ...Code to handle HTTP protocol.
    Else
       Throw New InvalidOperationException()
    End If   
    
    string protocol = GetProtocol(url);       
    if (String.Equals(protocol, "http")) {
       // ...Code to handle HTTP protocol.
    }
    else {
       throw new InvalidOperationException();
    }
    

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

Dim protocol As String = GetProtocol(url)       
If String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase) Then
   ' ...Code to handle HTTP protocol.
Else
   Throw New InvalidOperationException()
End If   
string protocol = GetProtocol(url);       
if (String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) {
   // ...Code to handle HTTP protocol.
}
else {
   throw new InvalidOperationException();
}

К началу

Подробные сведения по сравнению строк

Сравнение строк является основой большинства операций со строками, в частности сортировки и проверки на равенство. Строки сортируются в определенном порядке: если в упорядоченном списке строк "my" появляется до "string", то "my" должна сравниваться не больше "string". Кроме того, сравнение неявно задает равенство. Операция сравнения возвращает 0 для строк, которые она сочла равными. Правильная интерпретация этого – ни одна из строк не меньше другой. Наиболее значимые операции со строками включают одну или обе процедуры: сравнение с другой строкой и выполнение правильно определенной операции сортировки.

Однако оценка равенства двух строк или порядка сортировки не гарантирует единственный правильный результат; результат зависит от условия, которое использовалось для сравнения строк. В частности, порядковые сравнения строк и сравнения на основе правил использования прописных и строчных букв и сортировки текущих языка и региональных параметров или инвариантных (независимых от языковых стандартов языка и региональных параметров на основе английского языка) могут приводить к разным результатам.

Сравнения строк, использующие текущие язык и региональные параметры

Один критерий включает использование при сравнении строк правил текущего языка и региональных параметров. Сравнения на основе текущего языка и региональных параметров используют текущий язык и региональные параметры или языковый стандарт потока. Если язык и региональные параметры не заданы пользователем, то по умолчанию принимается значение, установленное в окне Региональные параметры панели управления. Следует всегда использовать сравнения на основе текущего языка и региональных параметров, если сравниваются лингвистические данные, которые отражают взаимодействие с пользователем, учитывающее язык и региональные параметры.

Однако при изменении языка и региональных параметров поведение сравнения и правил использования прописных и строчных букв в .NET Framework изменяется. Это происходит, когда приложение выполняется на компьютере с языком и региональными параметрами, отличными от тех, которые имелись на компьютере разработки приложения, или когда поток выполнения изменяет язык и региональные параметры. Это преднамеренное поведение, но неочевидное для многих разработчиков. В следующем примере показаны различия между американским английским ("en-US") языком и региональными параметрами и шведским ("sv-SE") языком и региональными параметрами. Обратите внимание, что слова "ångström", "Windows" и "Visual Studio" появляются в упорядоченных массивах строк в разных позициях.

Imports System.Globalization
Imports System.Threading

Module Example
   Public Sub Main()
      Dim values() As String = { "able", "ångström", "apple", _
                                 "Æble", "Windows", "Visual Studio" }
      Array.Sort(values)
      DisplayArray(values)

      ' Change culture to Swedish (Sweden).
      Dim originalCulture As String = CultureInfo.CurrentCulture.Name
      Thread.CurrentThread.CurrentCulture = New CultureInfo("sv-SE")
      Array.Sort(values)
      DisplayArray(values)

      ' Restore the original culture.
      Thread.CurrentThread.CurrentCulture = New CultureInfo(originalCulture)
    End Sub

    Private Sub DisplayArray(values() As String)
      Console.WRiteLine("Sorting using the {0} culture:", _ 
                        CultureInfo.CurrentCulture.Name)
      For Each value As String In values
         Console.WriteLine("   {0}", value)
      Next
      Console.WriteLine()   
    End Sub
End Module
' The example displays the following output:
'       Sorting using the en-US culture:
'          able
'          Æble
'          ångström
'          apple
'          Visual Studio
'          Windows
'       
'       Sorting using the sv-SE culture:
'          able
'          Æble
'          apple
'          Windows
'          Visual Studio
'          ångström
using System;
using System.Globalization;
using System.Threading;

public class Example
{
   public static void Main()
   {
      string[] values= { "able", "ångström", "apple", "Æble", 
                         "Windows", "Visual Studio" };
      Array.Sort(values);
      DisplayArray(values);

      // Change culture to Swedish (Sweden).
      string originalCulture = CultureInfo.CurrentCulture.Name;
      Thread.CurrentThread.CurrentCulture = new CultureInfo("sv-SE");
      Array.Sort(values);
      DisplayArray(values);

      // Restore the original culture.
      Thread.CurrentThread.CurrentCulture = new CultureInfo(originalCulture);
    }

    private static void DisplayArray(string[] values)
    {
      Console.WriteLine("Sorting using the {0} culture:",  
                        CultureInfo.CurrentCulture.Name);
      foreach (string value in values)
         Console.WriteLine("   {0}", value);

      Console.WriteLine();
    }
}
// The example displays the following output:
//       Sorting using the en-US culture:
//          able
//          Æble
//          ångström
//          apple
//          Visual Studio
//          Windows
//       
//       Sorting using the sv-SE culture:
//          able
//          Æble
//          apple
//          Windows
//          Visual Studio
//          ångström

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

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

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

При лингвистической интерпретации нелингвистических строковых данных или при интерпретации строковых данных определенного языка и региональных параметров с использованием правил другого языка и региональных параметров могут возникать трудно и не очень трудно выявляемые ошибки. Типичный пример – проблема с турецкой буквой I.

Почти во всех латинских алфавитах, включая американский английский, символ "i" (\u0069) является строчной версией символа "I" (\u0049). Это правило использования прописных и строчных букв стало само собой разумеющимся для тех, кто программирует для таких языков. Однако в турецком ("tr-TR") алфавите имеется буква "I с точкой", "İ" (\u0130), которая является прописной версией буквы "i". В турецком языке также имеется символ "i без точки", буква "ı" (\u0131), которая является строчной версией прописной буквы "I". Эта же особенность имеется и в азербайджанском языке ("az").

Следовательно, предположения о прописной "i" или строчной "I" не являются правильными для всех языков. При использовании перегрузок по умолчанию для процедур сравнения строк они будут меняться в зависимости от языка и региональных параметров. Если сравниваемые данные не являются лингвистическими, использование перегрузок по умолчанию может привести к нежелательным результатам, как показывает следующая попытка выполнения сравнения строк "file" и "FILE" без учета регистра.

Imports System.Globalization
Imports System.Threading

Module Example
   Public Sub Main()
      Dim fileUrl = "file"
      Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
      Console.WriteLine("Culture = {0}", _
                        Thread.CurrentThread.CurrentCulture.DisplayName)
      Console.WriteLine("(file == FILE) = {0}", _ 
                       fileUrl.StartsWith("FILE", True, Nothing))
      Console.WriteLine()

      Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")
      Console.WriteLine("Culture = {0}", _
                        Thread.CurrentThread.CurrentCulture.DisplayName)
      Console.WriteLine("(file == FILE) = {0}", _ 
                        fileUrl.StartsWith("FILE", True, Nothing))
   End Sub
End Module
' The example displays the following output:
'       Culture = English (United States)
'       (file == FILE) = True
'       
'       Culture = Turkish (Turkey)
'       (file == FILE) = False
using System;
using System.Globalization;
using System.Threading;

public class Example
{
   public static void Main()
   {
      string fileUrl = "file";
      Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
      Console.WriteLine("Culture = {0}",
                        Thread.CurrentThread.CurrentCulture.DisplayName);
      Console.WriteLine("(file == FILE) = {0}", 
                       fileUrl.StartsWith("FILE", true, null));
      Console.WriteLine();

      Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
      Console.WriteLine("Culture = {0}",
                        Thread.CurrentThread.CurrentCulture.DisplayName);
      Console.WriteLine("(file == FILE) = {0}", 
                        fileUrl.StartsWith("FILE", true, null));
   }
}
// The example displays the following output:
//       Culture = English (United States)
//       (file == FILE) = True
//       
//       Culture = Turkish (Turkey)
//       (file == FILE) = False

Это сравнение может вызвать значительные проблемы, если язык и региональные параметры случайно использовались в важных для обеспечения безопасности параметрах, как показано в следующем примере. Вызов метода, например IsFileURI("file:"), возвращает значение true, если текущий язык является американским английским, и значение false, если текущий язык – турецкий. Следовательно, в турецких системах кто-нибудь может обойти меры безопасности, блокирующие доступ к независящим от регистра универсальным кодам ресурсов (URI), начинающимся с "FILE:".

Public Shared Function IsFileURI(path As String) As Boolean 
   Return path.StartsWith("FILE:", True, Nothing)
End Function
public static bool IsFileURI(String path) 
{
   return path.StartsWith("FILE:", true, null);
}

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

Public Shared Function IsFileURI(path As String) As Boolean 
    Return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase)
End Function
public static bool IsFileURI(string path) 
{
   path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
   return true;
}

Однако в предыдущем примере для проверки на равенство используется метод String.StartsWith(String, StringComparison). Поскольку цель сравнения состоит в проверке на равенство, а не в упорядочивании строк, наилучшей альтернативой является вызов метода Equals, как показано в следующем примере.

Public Shared Function IsFileURI(path As String) As Boolean
   If (path.Length < 5) Then Return False

   Return String.Equals(path.Substring(0, 5), "FILE:", _ 
                        StringComparison.OrdinalIgnoreCase)
End Function   
public static bool IsFileURI(string path)
{
   if (path.Length < 5) return false;

   return String.Equals(path.Substring(0, 5), "FILE:", 
                        StringComparison.OrdinalIgnoreCase);
}   

Операции по порядковому сравнению строк

Указание значения StringComparison.Ordinal или StringComparison.OrdinalIgnoreCase в вызове метода означает нелингвистическое сравнение, при котором особенности национальных языков не учитываются. Методы, которые вызываются с этими значениями StringComparison, строят операции сравнения на простых побайтовых сравнениях вместо правил использования прописных и строчных букв или таблиц эквивалентности, параметризованных по языку и региональным параметрам. В большинстве случаев такой подход наилучшим образом соответствует предполагаемой интерпретации строк, делая код более быстрым и более читаемым.

Порядковые сравнения – это сравнения строк, при которых все байты каждой строки сравниваются без лингвистической интерпретации; например "windows" не соответствует "Windows". В сущности, это вызов функции strcmp среды выполнения C. Следует использовать такое сравнение, когда контекст указывает, что строки должны точно совпадать, или требует консервативную политику соответствия. Кроме того, порядковое сравнение является самой быстрой операцией сравнения, поскольку в нем не применяются лингвистические правила для определения результата.

Строки в .NET Framework могут содержать включенные символы NULL. Одно из самых очевидных различий между порядковым сравнением и сравнением с учетом языка и региональных параметров (включая сравнения, использующие инвариантный язык и региональные параметры) заключается в обработке включенных в строку символов NULL. При использовании методов String.Compare и String.Equals для выполнения сравнений с учетом языка и региональных параметров (включая сравнения, использующие инвариантный язык и региональные параметры) эти символы игнорируются. В результате при сравнениях, учитывающих язык и региональные параметры, строки, содержащие символы NULL, могут считаться равными строкам без этих символов.

Важное примечаниеВажно

Хотя методы сравнения строк пропускают включенные символы NULL, методы поиска строк, такие как String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOf и String.StartsWith, их не пропускают.

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

Module Example
   Public Sub Main()
      Dim str1 As String = "Aa"
      Dim str2 As String = "A" + New String(Convert.ToChar(0), 3) + "a"
      Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", _
                        str1, ShowBytes(str1), str2, ShowBytes(str2))
      Console.WriteLine("   With String.Compare:")
      Console.WriteLine("      Current Culture: {0}", _
                        String.Compare(str1, str2, StringComparison.CurrentCulture))
      Console.WriteLine("      Invariant Culture: {0}", _
                        String.Compare(str1, str2, StringComparison.InvariantCulture))

      Console.WriteLine("   With String.Equals:")
      Console.WriteLine("      Current Culture: {0}", _
                        String.Equals(str1, str2, StringComparison.CurrentCulture))
      Console.WriteLine("      Invariant Culture: {0}", _
                        String.Equals(str1, str2, StringComparison.InvariantCulture))
   End Sub

   Private Function ShowBytes(str As String) As String
      Dim hexString As String = String.Empty
      For ctr As Integer = 0 To str.Length - 1
         Dim result As String = String.Empty
         result = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
         result = " " + result.Substring(0,2) + " " + result.Substring(2, 2)
         hexString += result
      Next
      Return hexString.Trim()
   End Function
End Module
using System;

public class Example
{
   public static void Main()
   {
      string str1 = "Aa";
      string str2 = "A" + new String('\u0000', 3) + "a";
      Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", 
                        str1, ShowBytes(str1), str2, ShowBytes(str2));
      Console.WriteLine("   With String.Compare:");
      Console.WriteLine("      Current Culture: {0}", 
                        String.Compare(str1, str2, StringComparison.CurrentCulture));
      Console.WriteLine("      Invariant Culture: {0}", 
                        String.Compare(str1, str2, StringComparison.InvariantCulture));

      Console.WriteLine("   With String.Equals:");
      Console.WriteLine("      Current Culture: {0}", 
                        String.Equals(str1, str2, StringComparison.CurrentCulture));
      Console.WriteLine("      Invariant Culture: {0}", 
                        String.Equals(str1, str2, StringComparison.InvariantCulture));
   }

   private static string ShowBytes(string str)
   {
      string hexString = String.Empty;
      for (int ctr = 0; ctr < str.Length; ctr++)
      {
         string result = String.Empty;
         result = Convert.ToInt32(str[ctr]).ToString("X4");
         result = " " + result.Substring(0,2) + " " + result.Substring(2, 2);
         hexString += result;
      }
      return hexString.Trim();
   }
}
// The example displays the following output:
//    Comparing 'Aa' (00 41 00 61) and 'A   a' (00 41 00 00 00 00 00 00 00 61):
//       With String.Compare:
//          Current Culture: 0
//          Invariant Culture: 0
//       With String.Equals:
//          Current Culture: True
//          Invariant Culture: True

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

Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", _
                  str1, ShowBytes(str1), str2, ShowBytes(str2))
Console.WriteLine("   With String.Compare:")
Console.WriteLine("      Ordinal: {0}", _
                  String.Compare(str1, str2, StringComparison.Ordinal))

Console.WriteLine("   With String.Equals:")
Console.WriteLine("      Ordinal: {0}", _
                  String.Equals(str1, str2, StringComparison.Ordinal))
' The example displays the following output:
'    Comparing 'Aa' (00 41 00 61) and 'A   a' (00 41 00 00 00 00 00 00 00 61):
'       With String.Compare:
'          Ordinal: 97
'       With String.Equals:
'          Ordinal: False
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", 
                  str1, ShowBytes(str1), str2, ShowBytes(str2));
Console.WriteLine("   With String.Compare:");
Console.WriteLine("      Ordinal: {0}", 
                  String.Compare(str1, str2, StringComparison.Ordinal));

Console.WriteLine("   With String.Equals:");
Console.WriteLine("      Ordinal: {0}", 
                  String.Equals(str1, str2, StringComparison.Ordinal));
// The example displays the following output:
//    Comparing 'Aa' (00 41 00 61) and 'A   a' (00 41 00 00 00 00 00 00 00 61):
//       With String.Compare:
//          Ordinal: 97
//       With String.Equals:
//          Ordinal: False

Порядковые сравнения без учета регистра являются следующим из наиболее консервативных подходов. В таких сравнениях игнорируется большая часть правил использования прописных и строчных букв; например "windows" соответствует "Windows". При работе с символами ASCII эта политика эквивалентна StringComparison.Ordinal, за исключением того, что игнорирует обычные правила использования прописных и строчных букв ASCII. Следовательно, любой символ в диапазоне [A, Z] (\u0041-\u005A) равен соответствующему символу в диапазоне [a, z] (\u0061-\007A). Правила использования прописных и строчных букв вне диапазона ASCII используют таблицы инвариантного языка и региональных параметров. Таким образом, сравнение

String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);

эквивалентно следующему сравнению (но гораздо быстрее):

String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), 
               StringComparison.Ordinal)
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), 
               StringComparison.Ordinal);

Оба эти сравнения довольно быстрые.

ПримечаниеПримечание

Поведение строк файловой системы, разделов и значений реестра и переменных среды наилучшим образом представляется сравнением StringComparison.OrdinalIgnoreCase.

Оба сравнения, StringComparison.Ordinal и StringComparison.OrdinalIgnoreCase, используют напрямую двоичные значения и наиболее подходят для сопоставления. Если нет уверенности насчет параметров сравнения, следует использовать одно из этих двух значений. Однако поскольку они используют побайтовое сравнение, упорядочивание выполняется не в соответствии с лингвистическим порядком сортировки (как в английском словаре), а в соответствии с двоичным порядком сортировки. В большинстве контекстов результат может отображаться для пользователя с искажениями.

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

Операции со строками, использующие инвариантный язык и региональные параметры

Сравнения с учетом инвариантного языка и региональных параметров используют свойство CompareInfo, возвращаемое статическим свойством CultureInfo.InvariantCulture. Это поведение одинаково во всех системах; оно преобразовывает любые символы вне собственного диапазона в те, которые с его точки зрения эквивалентны инвариантным символам. Эта политика может пригодиться для обслуживания одного набора поведения строк в нескольких средах языков и региональных параметров, но часто приводит к неожиданным результатам.

Сравнения без учета регистра и с учетом инвариантного языка и региональных параметров также используют для получения сведений о сравнении статическое свойство CompareInfo, возвращаемое статическим свойством CultureInfo.InvariantCulture. Любые различия регистров в этих преобразуемых символах игнорируются.

Сравнения. использующие StringComparison.InvariantCulture и StringComparison.Ordinal, работают со строками ASCII одинаково. Однако StringComparison.InvariantCulture принимает лингвистические решения, которые могут не подходить для строк, которые интерпретируются как набор байтов. Объект CultureInfo.InvariantCulture.CompareInfo создает метод Compare, который интерпретирует разные наборы символов как равные. Например, для инвариантного языка и региональных параметров верно следующее равенство:

InvariantCulture: a + ̊ = å

Символ "a" (\u0061), обозначающий СТРОЧНУЮ ЛАТИНСКУЮ БУКВУ A, находящийся рядом с символом ПРИСОЕДИНЕНИЯ ВЕРХНЕГО КРУЖКА "+ ̊" (\u030a), интерпретируется как символ СТРОЧНОЙ ЛАТИНСКОЙ БУКВЫ A С КРУЖКОМ ВВЕРХУ "å" (\u00e5). Как показано в следующем примере, это поведение отличается от поведения порядкового сравнения.

Dim separated As String = ChrW(&h61) + ChrW(&h30a)
Dim combined As String = ChrW(&he5)

Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}", _
                  separated, combined, _
                  String.Compare(separated, combined, _ 
                                 StringComparison.InvariantCulture) = 0)

Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}", _
                  separated, combined, _
                  String.Compare(separated, combined, _
                                 StringComparison.Ordinal) = 0)
' The example displays the following output:
'    Equal sort weight of a° and å using InvariantCulture: True
'    Equal sort weight of a° and å using Ordinal: False
string separated = "\u0061\u030a";
string combined = "\u00e5";

Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}",
                  separated, combined, 
                  String.Compare(separated, combined, 
                                 StringComparison.InvariantCulture) == 0);

Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}",
                  separated, combined,
                  String.Compare(separated, combined, 
                                 StringComparison.Ordinal) == 0);
// The example displays the following output:
//    Equal sort weight of a° and å using InvariantCulture: True
//    Equal sort weight of a° and å using Ordinal: False      

При интерпретации имен файлов, файлов Cookie или других объектов, в которых может встречаться такая комбинация, как "å", порядковые сравнения все еще предлагают наиболее прозрачное и подходящее поведение.

В итоге инвариантный язык и региональные параметры имеют очень мало свойств, которые делают их подходящими для использования при сравнении. Сравнение выполняется лингвистическим способом, что препятствует гарантии полного равенства символов, но это не является выбором для отображения в среде какого-либо языка и региональных параметров. Одной из немногих причин использования StringComparison.InvariantCulture для сравнения является наличие упорядоченных данных, что обеспечивает одинаковое отображение для всех языков и региональных параметров. Например, если к приложению прилагается большой файл данных, содержащий список упорядоченных идентификаторов для отображения, дополнение этого списка потребует вставку с сортировкой в инвариантном стиле.

К началу

Выбор элемента StringComparison для вызова метода

В следующей таблице приведено сопоставление семантического контекста строк с элементом перечисления StringComparison.

Данные

Назначение

Соответствующее System.StringComparison

value

Внутренние идентификаторы с учетом регистра.

Внутренние идентификаторы с учетом регистра в таких стандартах, как XML и HTTP.

Параметры, относящиеся к безопасности, с учетом регистра.

Не лингвистический идентификатор, в котором байты сопоставляются точно.

Ordinal

Внутренние идентификаторы без учета регистра.

Внутренние идентификаторы без учета регистра в таких стандартах, как XML и HTTP.

Пути файлов.

Разделы и значения реестра.

Переменные среды.

Идентификаторы ресурсов (например имена дескрипторов).

Параметры, относящиеся к безопасности, без учета регистра.

Не лингвистические идентификаторы, в которых регистр не учитывается; в частности данные, хранящиеся в большинстве системных служб Windows.

OrdinalIgnoreCase

Некоторые хранящиеся лингвистические данные.

Отображение лингвистических данных, требующее фиксированный порядок сортировки.

Данные, не зависящие от языка и региональных параметров, но являющиеся лингвистическими.

InvariantCulture

– или –

InvariantCultureIgnoreCase

Отображаемые для пользователя данные.

Большая часть ввода пользователя.

Данные, которым необходимы локальные лингвистические настройки.

CurrentCulture

– или –

CurrentCultureIgnoreCase

К началу

Наиболее распространенные методы сравнения строк в .NET Framework

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

String.Compare

Интерпретация по умолчанию: StringComparison.CurrentCulture.

Поскольку операция является основной для интерпретации строк, все экземпляры этих вызовов методов необходимо проверить, чтобы определить, должны ли строки интерпретироваться в соответствии с текущим языком и региональными параметрами или отдельно от языка и региональных параметров (по символам). Обычно выбирается последнее, и тогда должно использоваться сравнение StringComparison.Ordinal.

Класс System.Globalization.CompareInfo, возвращаемый свойством CultureInfo.CompareInfo, также включает метод Compare, который предоставляет большое количество вариантов сопоставления (порядковое, без учета пробелов, без учета типа каны и т.п.) с помощью перечисления флагов CompareOptions.

String.CompareTo

Интерпретация по умолчанию: StringComparison.CurrentCulture.

Этот метод в настоящий момент не предлагает перегрузку, задающую тип StringComparison. Обычно возможно преобразовать эти методы в рекомендованную форму String.Compare(String, String, StringComparison).

Типы, реализующие интерфейсы IComparable и IComparable<T>, реализуют этот метод. Поскольку в нем не предусмотрен параметр StringComparison, реализуемые типы часто дают пользователям возможность указать StringComparer в своем конструкторе. В следующем примере определяется класс FileName, чей конструктор включает параметр StringComparer. Затем этот объект StringComparer используется в методе FileName.CompareTo.

Public Class FileName : Implements IComparable
   Dim fname As String
   Dim comparer As StringComparer 

   Public Sub New(name As String, comparer As StringComparer)
      If String.IsNullOrEmpty(name) Then
         Throw New ArgumentNullException("name")
      End If

      Me.fname = name

      If comparer IsNot Nothing Then
         Me.comparer = comparer
      Else
         Me.comparer = StringComparer.OrdinalIgnoreCase
      End If      
   End Sub

   Public ReadOnly Property Name As String
      Get
         Return fname
      End Get   
   End Property

   Public Function CompareTo(obj As Object) As Integer _
          Implements IComparable.CompareTo
      If obj Is Nothing Then Return 1

      If Not TypeOf obj Is FileName Then
         obj = obj.ToString()
      Else
         obj = CType(obj, FileName).Name
      End If         
      Return comparer.Compare(Me.fname, obj)
   End Function
End Class
using System;

public class FileName : IComparable
{
   string fname;
   StringComparer comparer; 

   public FileName(string name, StringComparer comparer)
   {
      if (String.IsNullOrEmpty(name))
         throw new ArgumentNullException("name");

      this.fname = name;

      if (comparer != null)
         this.comparer = comparer;
      else
         this.comparer = StringComparer.OrdinalIgnoreCase;
   }

   public string Name
   {
      get { return fname; }
   }

   public int CompareTo(object obj)
   {
      if (obj == null) return 1;

      if (! (obj is FileName))
         return comparer.Compare(this.fname, obj.ToString());
      else
         return comparer.Compare(this.fname, ((FileName) obj).Name);
   }
}

String.Equals

Интерпретация по умолчанию: StringComparison.Ordinal.

Класс String предоставляет возможность проверки на равенство путем вызова перегруженного статического метода или перегруженного метода экземпляра Equals. По умолчанию перегрузки и оператор используют порядковое сравнение. Однако рекомендуется вызывать перегрузку, явно задающую тип StringComparison, даже если планируется выполнять порядковое сравнение; это облегчит поиск кода для разных интерпретаций строк.

String.ToUpper и String.ToLower

Интерпретация по умолчанию: StringComparison.CurrentCulture.

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

Возможно также использование методов String.ToUpperInvariant и String.ToLowerInvariant. Метод ToUpperInvariant – это стандартный способ нормализации регистра. Сравнения, которые делаются с помощью StringComparison.OrdinalIgnoreCase, поведенчески являются объединением двух вызовов: вызова метода ToUpperInvariant для обоих строковых аргументов и сравнения с помощью метода StringComparison.Ordinal.

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

Char.ToUpper и Char.ToLower

Интерпретация по умолчанию: StringComparison.CurrentCulture.

Эти методы работают аналогично методам String.ToUpper и String.ToLower, которые рассматривались в предыдущем разделе.

String.StartsWith и String.EndsWith

Интерпретация по умолчанию: StringComparison.CurrentCulture.

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

String.IndexOf и String.LastIndexOf

Интерпретация по умолчанию: StringComparison.CurrentCulture.

Имеется некоторая непоследовательность в том, как перегрузки этих методов по умолчанию выполняют сравнения. Все методы String.IndexOf и String.LastIndexOf, включающие параметр Char, выполняют порядковое сравнение, но методы по умолчанию String.IndexOf и String.LastIndexOf, включающие параметр String, выполняют сравнение с учетом языка и региональных параметров.

При вызове метода String.IndexOf(String) или String.LastIndexOf(String) и передаче его в строку для обнаружения в текущем экземпляре рекомендуется вызывать перегрузку, которая явно указывает тип StringComparison. Перегрузки, включающие аргумент Char, не разрешают указывать тип StringComparison.

К началу

Методы неявного сравнения строк

Некоторые не строковые методы, основной операцией которых является сравнение строк, используют тип StringComparer. Класс StringComparer включает шесть статических свойств, возвращающих экземпляры StringComparer, методы StringComparer.Compare которых выполняют следующие типы сравнений строк:

  • Сравнения строк с учетом языка и региональных параметров, использующие текущий язык и региональные параметры. Этот объект StringComparer возвращается свойством StringComparer.CurrentCulture.

  • Сравнения без учета регистра и с использованием текущего языка и региональных параметров. Этот объект StringComparer возвращается свойством StringComparer.CurrentCultureIgnoreCase.

  • Сравнения без учета языка и региональных параметров, использующие правила сравнения слов инвариантного языка и региональных параметров. Этот объект StringComparer возвращается свойством StringComparer.InvariantCulture.

  • Сравнения без учета регистра, языка и региональных параметров, использующие правила сравнения слов инвариантного языка и региональных параметров. Этот объект StringComparer возвращается свойством StringComparer.InvariantCultureIgnoreCase.

  • Порядковое сравнение. Этот объект StringComparer возвращается свойством StringComparer.Ordinal.

  • Порядковое сравнение без учета регистра. Этот объект StringComparer возвращается свойством StringComparer.OrdinalIgnoreCase.

Array.Sort и Array.BinarySearch

Интерпретация по умолчанию: StringComparison.CurrentCulture.

При сохранении каких-либо данных в коллекции или считывании существующих данных из файла или базы данных в коллекцию переключение текущего языка и региональных параметров может сделать инварианты в коллекции недействительными. Метод Array.BinarySearch предполагает, что элементы массива, которые следует искать, уже отсортированы. Для сортировки каких-либо строковых элементов в массиве метод Array.Sort вызывает метод String.Compare для упорядочивания отдельных элементов. Использование механизма сравнения, учитывающего язык и региональные параметры, может быть опасным, если в промежутке между сортировкой массива и поиском по его содержимому язык и региональные параметры изменятся. Например, в следующем коде операции хранения и извлечения работают с механизмом сравнения, который неявно предоставляется свойством Thread.CurrentThread.CurrentCulture. Если может произойти изменение языка и региональных параметров между вызовами StoreNames и DoesNameExist, и особенно если содержимое массива постоянно присутствует где-нибудь еще между этими двумя вызовами методов, двоичный поиск может завершиться неудачно.

' Incorrect.
Dim storedNames() As String

Public Sub StoreNames(names() As String)
   Dim index As Integer = 0
   ReDim storedNames(names.Length - 1)

   For Each name As String In names
      Me.storedNames(index) = name
      index+= 1
   Next

   Array.Sort(names)          ' Line A.
End Sub

Public Function DoesNameExist(name As String) As Boolean
   Return Array.BinarySearch(Me.storedNames, name) >= 0      ' Line B.
End Function
// Incorrect.
string []storedNames;

public void StoreNames(string [] names)
{
   int index = 0;
   storedNames = new string[names.Length];

   foreach (string name in names)
   {
      this.storedNames[index++] = name;
   }

   Array.Sort(names); // Line A.
}

public bool DoesNameExist(string name)
{
   return (Array.BinarySearch(this.storedNames, name) >= 0);  // Line B.
}

Рекомендуемый вариант показан в следующем примере, в котором для сортировки массива и для поиска в нем используется одно и то же порядковое (не учитывающее язык и региональные параметры) сравнение. Изменения кода показаны в строках с метками Line A и Line B в этих двух примерах.

' Correct.
Dim storedNames() As String

Public Sub StoreNames(names() As String)
   Dim index As Integer = 0
   ReDim storedNames(names.Length - 1)

   For Each name As String In names
      Me.storedNames(index) = name
      index+= 1
   Next

   Array.Sort(names, StringComparer.Ordinal)           ' Line A.
End Sub

Public Function DoesNameExist(name As String) As Boolean
   Return Array.BinarySearch(Me.storedNames, name, StringComparer.Ordinal) >= 0      ' Line B.
End Function
// Correct.
string []storedNames;

public void StoreNames(string [] names)
{
   int index = 0;
   storedNames = new string[names.Length];

   foreach (string name in names)
   {
      this.storedNames[index++] = name;
   }

   Array.Sort(names, StringComparer.Ordinal);  // Line A.
}

public bool DoesNameExist(string name)
{
   return (Array.BinarySearch(this.storedNames, name, StringComparer.Ordinal) >= 0);  // Line B.
}

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

' Correct.
Dim storedNames() As String

Public Sub StoreNames(names() As String)
   Dim index As Integer = 0
   ReDim storedNames(names.Length - 1)

   For Each name As String In names
      Me.storedNames(index) = name
      index+= 1
   Next

   Array.Sort(names, StringComparer.InvariantCulture)           ' Line A.
End Sub

Public Function DoesNameExist(name As String) As Boolean
   Return Array.BinarySearch(Me.storedNames, name, StringComparer.InvariantCulture) >= 0      ' Line B.
End Function
// Correct.
string []storedNames;

public void StoreNames(string [] names)
{
   int index = 0;
   storedNames = new string[names.Length];

   foreach (string name in names)
   {
      this.storedNames[index++] = name;
   }

   Array.Sort(names, StringComparer.InvariantCulture);  // Line A.
}

public bool DoesNameExist(string name)
{
   return (Array.BinarySearch(this.storedNames, name, StringComparer.InvariantCulture) >= 0);  // Line B.
}

Пример коллекций. Конструктор таблицы хэширования

Хэшированные строки обеспечивают второй пример операции, на которую влияет способ сравнения строк.

В следующем примере объект Hashtable создается путем передачи его в объект StringComparer, возвращаемый свойством StringComparer.OrdinalIgnoreCase. Поскольку класс StringComparer, производный от StringComparer, реализует интерфейс IEqualityComparer, его метод GetHashCode используется для вычисления хэш-кода строк в хэш-таблице.

Const initialTableCapacity As Integer = 100
Dim h As Hashtable

Public Sub PopulateFileTable(dir As String)
   h = New Hashtable(initialTableCapacity, _
                     StringComparer.OrdinalIgnoreCase)

   For Each filename As String In Directory.GetFiles(dir)
      h.Add(filename, File.GetCreationTime(filename))
   Next                        
End Sub

Public Sub PrintCreationTime(targetFile As String)
   Dim dt As Object = h(targetFile)
   If dt IsNot Nothing Then
      Console.WriteLine("File {0} was created at {1}.", _
         targetFile, _
         CDate(dt))
   Else
      Console.WriteLine("File {0} does not exist.", targetFile)
   End If
End Sub  
const int initialTableCapacity = 100;
Hashtable h;

public void PopulateFileTable(string directory)
{
   h = new Hashtable(initialTableCapacity, 
                     StringComparer.OrdinalIgnoreCase);

   foreach (string file in Directory.GetFiles(directory))
         h.Add(file, File.GetCreationTime(file));
}

public void PrintCreationTime(string targetFile)
{
   Object dt = h[targetFile];
   if (dt != null)
   {
      Console.WriteLine("File {0} was created at time {1}.",
         targetFile, 
         (DateTime) dt);
   }
   else
   {
      Console.WriteLine("File {0} does not exist.", targetFile);
   }
}

К началу