Рекомендации по использованию строк в .NETBest Practices for Using Strings in .NET

.NET предоставляет широкие возможности для разработки локализованных и глобализованных приложений и позволяет легко применять правила для текущего или какого-то определенного языка и региональных параметров при выполнении типичных операций, таких как сортировка и отображение строк..NET provides extensive support for developing localized and globalized applications, and makes it easy to apply the conventions of either the current culture or a specific culture when performing common operations such as sorting and displaying strings. Однако сортировка и сравнение строк не всегда выполняется с учетом языка и региональных параметров.But sorting or comparing strings is not always a culture-sensitive operation. Например, строки, которые используются внутри приложения, как правило, должны обрабатываться одинаково независимо от выбранного языка и региональных параметров.For example, strings that are used internally by an application typically should be handled identically across all cultures. Если независимые от языка и региональных параметров строковые данные, такие как теги XML, теги HTML, имена пользователей, пути к файлам и имена системных объектов, интерпретируются как зависимые от языка и региональных параметров, в коде приложения могут возникать незначительные ошибки, может наблюдаться низкая производительность, а в некоторых случаях и проблемы безопасности.When culturally independent string data, such as XML tags, HTML tags, user names, file paths, and the names of system objects, are interpreted as if they were culture-sensitive, application code can be subject to subtle bugs, poor performance, and, in some cases, security issues.

В этой статье рассматриваются методы .NET для сортировки, сравнения строк и изменения регистра, представлены рекомендации по выбору подходящего метода обработки строк и дополнительная информация об этих методах.This topic examines the string sorting, comparison, and casing methods in .NET, presents recommendations for selecting an appropriate string-handling method, and provides additional information about string-handling methods. Кроме того, рассматривается отображение и хранение форматированных данных, например числовых данных или даты и времени.It also examines how formatted data, such as numeric data and date and time data, is handled for display and for storage.

В этом разделе содержатся следующие подразделы.This topic contains the following sections:

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

Если вы выполняете разработку на платформе .NET, следуйте нескольким простым рекомендациям по работе со строками.When you develop with .NET, follow these simple recommendations when you use strings:

При использовании строк избегайте следующих действий.Avoid the following practices when you use strings:

  • Не используйте перегрузки, которые не задают правила сравнения строк для операций со строками в явной или неявной форме.Do not use overloads that do not explicitly or implicitly specify the string comparison rules for string operations.

  • В большинстве случаев не стоит использовать строковые операции, основанные на StringComparison.InvariantCulture .Do not use string operations based on StringComparison.InvariantCulture in most cases. Одним из немногочисленных исключений является случай сохранения лингвистически значимых данных, которые, тем не менее, не зависят от языка и региональных параметров.One of the few exceptions is when you are persisting linguistically meaningful but culturally agnostic data.

  • Не используйте перегрузку метода String.Compare или CompareTo и проверяйте возвращаемое значение (ноль), чтобы определить, равны ли строки.Do not use an overload of the String.Compare or CompareTo method and test for a return value of zero to determine whether two strings are equal.

  • Не используйте форматирование с учетом языка и региональных параметров для сохранения числовых данных или данных даты и времени в виде строки.Do not use culture-sensitive formatting to persist numeric data or date and time data in string form.

К началуBack to top

Явное задание сравнений строкSpecifying String Comparisons Explicitly

Большинство методов обработки строк в .NET являются перегруженными.Most of the string manipulation methods in .NET are overloaded. Как правило, одна или несколько перегрузок принимают настройки по умолчанию, а другие — нет и вместо этого определяют требуемый точный способ сравнения и обработки строк.Typically, one or more overloads accept default settings, whereas others accept no defaults and instead define the precise way in which strings are to be compared or manipulated. Большинство методов, не использующих значения по умолчанию, включают параметр типа StringComparison, который представляет собой перечисление, явно задающее правила сравнения строк по языку, региональным параметрам и регистру.Most of the methods that do not rely on defaults include a parameter of type StringComparison, which is an enumeration that explicitly specifies rules for string comparison by culture and case. В следующей таблице описаны элементы перечисления StringComparison .The following table describes the StringComparison enumeration members.

Элемент StringComparisonStringComparison member ОПИСАНИЕDescription
CurrentCulture Выполняет сравнение с учетом регистра, используя текущий язык и региональные параметры.Performs a case-sensitive comparison using the current culture.
CurrentCultureIgnoreCase Выполняет сравнение без учета регистра, используя текущий язык и региональные параметры.Performs a case-insensitive comparison using the current culture.
InvariantCulture Выполняет сравнение с учетом регистра, используя инвариантный язык и региональные параметры.Performs a case-sensitive comparison using the invariant culture.
InvariantCultureIgnoreCase Выполняет сравнение без учета регистра, используя инвариантный язык и региональные параметры.Performs a case-insensitive comparison using the invariant culture.
Ordinal Выполняет порядковое сравнение.Performs an ordinal comparison.
OrdinalIgnoreCase Выполняет порядковое сравнение без учета регистра.Performs a case-insensitive ordinal comparison.

Например, метод IndexOf , который возвращает индекс подстроки в объект String , соответствующий символу или строке, имеет девять перегрузок.For example, the IndexOf method, which returns the index of a substring in a String object that matches either a character or a string, has nine overloads:

Рекомендуется выбрать перегрузку, не использующую значения по умолчанию, по следующим причинам.We recommend that you select an overload that does not use default values, for the following reasons:

  • Некоторые перегрузки с параметрами по умолчанию (те, которые выполняют поиск Char в экземпляре строки) выполняют порядковое сравнение, в то время как другие (выполняющие поиск строки в экземпляре строки) учитывают язык и региональные параметры.Some overloads with default parameters (those that search for a Char in the string instance) perform an ordinal comparison, whereas others (those that search for a string in the string instance) are culture-sensitive. Сложно запомнить, какое значение по умолчанию использует тот или иной метод, перегрузки легко перепутать.It is difficult to remember which method uses which default value, and easy to confuse the overloads.

  • Назначение кода, вызовы методов в котором зависят от значений по умолчанию, не ясно.The intent of the code that relies on default values for method calls is not clear. В следующем примере, где используются значения по умолчанию, сложно определить, действительно ли разработчик намеревался выполнить порядковое или лингвистическое сравнение двух строк или различие регистра между protocol и http могло привести к тому, что проверка на равенство возвратит значение false.In the following example, which relies on defaults, it is difficult to know whether the developer actually intended an ordinal or a linguistic comparison of two strings, or whether a case difference between protocol and "http" might cause the test for equality to return false.

    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") Then
       ' ...Code to handle HTTP protocol.
    Else
       Throw New InvalidOperationException()
    End If   
    

В общем случае рекомендуется вызывать метод, который не зависит от значений по умолчанию, поскольку это делает назначение кода однозначным.In general, we recommend that you call a method that does not rely on defaults, because it makes the intent of the code unambiguous. Это, в свою очередь, делает код более читаемым и упрощает отладку и обслуживание.This, in turn, makes the code more readable and easier to debug and maintain. В следующем примере рассматриваются вопросы, возникшие в предыдущем примере.The following example addresses the questions raised about the previous example. Он явно демонстрирует, что используется порядковое сравнение и что различия регистра игнорируются.It makes it clear that ordinal comparison is used and that differences in case are ignored.

string protocol = GetProtocol(url);       
if (String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) {
   // ...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   

К началуBack to top

Подробные сведения о сравнении строкThe Details of String Comparison

Сравнение строк является основой многих связанных со строками операций, в частности сортировки и проверки на равенство.String comparison is the heart of many string-related operations, particularly sorting and testing for equality. Строки сортируются в определенном порядке: если my отображается до string в сортированном списке строк, текст my должен быть меньше или равен тексту string.Strings sort in a determined order: If "my" appears before "string" in a sorted list of strings, "my" must compare less than or equal to "string". Кроме того, неявное сравнение определяет равенство.Additionally, comparison implicitly defines equality. Операция сравнения возвращает 0 для строк, которые она считает равными.The comparison operation returns zero for strings it deems equal. Правильно интерпретировать это следующим образом: ни одна из строк не меньше другой.A good interpretation is that neither string is less than the other. Наиболее значимые операции со строками включают обе следующие процедуры или хотя бы одну из них: сравнение с другой строкой и выполнение правильно определенной операции сортировки.Most meaningful operations involving strings include one or both of these procedures: comparing with another string, and executing a well-defined sort operation.

Примечание

Можно скачать таблицы коэффициентов сортировки — набор текстовых файлов, которые содержат сведения о весовых коэффициентах символов, используемых в операциях сортировки и сравнения для операционных систем Windows, а также последнюю версию таблицы параметров сортировки по умолчанию для элементов Юникод — таблицу весовых коэффициентов сортировки для Linux и macOS.You can download the Sorting Weight Tables, a set of text files that contain information on the character weights used in sorting and comparison operations for Windows operating systems, and the Default Unicode Collation Element Table, the latest version of the sort weight table for Linux and macOS. Конкретная версия таблицы коэффициентов сортировки в Linux и macOS зависит от установленной в системе версии библиотек International Components for Unicode (ICU).The specific version of the sort weight table on Linux and macOS depends on the version of the International Components for Unicode libraries installed on the system. Сведения о версиях ICU и реализуемых в них версиях Юникода см. на странице Downloading ICU (Скачивание ICU).For information on ICU versions and the Unicode versions that they implement, see Downloading ICU.

Однако оценка двух строк на равенство или порядок сортировки не дает единственно верного результата; результат также зависит от критериев, используемых для сравнения строк.However, evaluating two strings for equality or sort order does not yield a single, correct result; the outcome depends on the criteria used to compare the strings. В частности, операции сравнения строк, которые являются порядковыми или основаны на правилах учета регистра или сортировки текущего языка и региональных параметров или инвариантного языка и региональных параметров (независимые от языкового стандарта региональные параметры на основе английского языка), могут давать разные результаты.In particular, string comparisons that are ordinal or that are based on the casing and sorting conventions of the current culture or the invariant culture (a locale-agnostic culture based on the English language) may produce different results.

Кроме того, операции сравнения, которые выполняются в разных версиях .NET или с помощью .NET в разных операционных системах либо в разных версиях операционной системы, могут возвращать разные результаты.In addition, string comparisons using different versions of .NET or using .NET on different operating systems or operating system versions may return different results. Дополнительные сведения см. в разделе Строки и стандарт Юникода.For more information, see Strings and the Unicode Standard.

Сравнение строк с использованием текущего языка и региональных параметровString Comparisons that Use the Current Culture

Одним из критериев является использование правил текущего языка и региональных параметров при сравнении строк.One criterion involves using the conventions of the current culture when comparing strings. В сравнениях, основанных на текущем языке и региональных параметрах, используется текущий язык, региональные параметры или языковой стандарт потока.Comparisons that are based on the current culture use the thread's current culture or locale. Если пользователь не задал язык и региональные параметры, используется настройка по умолчанию в окне Региональные параметры на панели управления.If the culture is not set by the user, it defaults to the setting in the Regional Options window in Control Panel. Следует всегда использовать сравнения на основе текущего языка и региональных параметров, если речь идет о лингвистически релевантных данных и данных, отражающих взаимодействие с пользователем, где важны язык и региональные параметры.You should always use comparisons that are based on the current culture when data is linguistically relevant, and when it reflects culture-sensitive user interaction.

Однако поведение сравнения и использования регистра в .NET меняется при изменении языка и региональных параметров.However, comparison and casing behavior in .NET changes when the culture changes. Это происходит, если приложение выполняется на компьютере с другим языком и региональными параметрами, нежели на компьютере, где приложение было разработано, либо если выполняющий поток меняет свой язык и региональные параметры.This happens when an application executes on a computer that has a different culture than the computer on which the application was developed, or when the executing thread changes its culture. Это поведение является преднамеренным, однако до сих пор остается неочевидным для многих разработчиков.This behavior is intentional, but it remains non-obvious to many developers. В следующем примере показаны различия в порядке сортировки в американском английском (en-US) и шведском языке (sv-SE).The following example illustrates differences in sort order between the U.S. English ("en-US") and Swedish ("sv-SE") cultures. Обратите внимание, что слова ångström, Windows и Visual Studio показаны в разных местах массива сортированных строк.Note that the words "ångström", "Windows", and "Visual Studio" appear in different positions in the sorted string arrays.

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

Сравнения без учета регистра, где используются текущий язык и региональные параметры, выполняются так же, как сравнения с учетом языка и региональных параметров с той разницей, что регистр игнорируется в соответствии с правилами текущего языка и региональных параметров потока.Case-insensitive comparisons that use the current culture are the same as culture-sensitive comparisons, except that they ignore case as dictated by the thread's current culture. Это поведение может также проявляться в порядке сортировки.This behavior may manifest itself in sort orders as well.

Сравнения, использующие семантику текущего языка и региональных параметров, используются по умолчанию для следующих методов.Comparisons that use current culture semantics are the default for the following methods:

В любом случае рекомендуется вызвать перегрузку, имеющую параметр StringComparison , чтобы сделать назначение вызова метода очевидным.In any case, we recommend that you call an overload that has a StringComparison parameter to make the intent of the method call clear.

При лингвистической интерпретации нелингвистических строковых данных, а также если строковые данные определенного языка и региональных параметров интерпретируются с использованием правил другого языка, могут возникать малозаметные и не столь малозаметные ошибки.Subtle and not so subtle bugs can emerge when non-linguistic string data is interpreted linguistically, or when string data from a particular culture is interpreted using the conventions of another culture. Типичный пример — проблема турецкого I.The canonical example is the Turkish-I problem.

Почти во всех латинских алфавитах, включая американский английский, символ i (\u0069) является строчной версией символа I (\u0049).For nearly all Latin alphabets, including U.S. English, the character "i" (\u0069) is the lowercase version of the character "I" (\u0049). Это правило учета регистра быстро становится значением по умолчанию для тех, кто программирует для этих языков.This casing rule quickly becomes the default for someone programming in such a culture. Однако в турецком алфавите (tr-TR) используется I с точкой — İ (\u0130), которая является прописной версией i.However, the Turkish ("tr-TR") alphabet includes an "I with a dot" character "İ" (\u0130), which is the capital version of "i". В турецком языке также есть строчная i без точки, ı (\u0131), прописной для которой является I.Turkish also includes a lowercase "i without a dot" character, "ı" (\u0131), which capitalizes to "I". Эта же особенность имеется и в азербайджанском языке ("az").This behavior occurs in the Azerbaijani ("az") culture as well.

Таким образом, допущения о прописной версии буквы i или строчной версии буквы I не являются правильными для всех языков.Therefore, assumptions made about capitalizing "i" or lowercasing "I" are not valid among all cultures. При использовании перегрузок по умолчанию для сравнения строк они будут меняться в зависимости от языков и региональных параметров.If you use the default overloads for string comparison routines, they will be subject to variance between cultures. Если сравниваются нелингвистические данные, при использовании перегрузок по умолчанию может быть получен нежелательный результат, как показывает следующий пример с попыткой сравнить строки file и FILE без учета регистра.If the data to be compared is non-linguistic, using the default overloads can produce undesirable results, as the following attempt to perform a case-insensitive comparison of the strings "file" and "FILE" illustrates.

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

Это сравнение может вызвать значительные проблемы, если язык и региональные параметры случайно использовались в конфиденциальных параметрах, как показано в следующем примере.This comparison could cause significant problems if the culture is inadvertently used in security-sensitive settings, as in the following example. Вызов метода, например IsFileURI("file:"), возвращает значение true, если текущий язык — американский английский, и значение false, если текущий язык — турецкий.A method call such as IsFileURI("file:") returns true if the current culture is U.S. English, but false if the current culture is Turkish. Следовательно, в системах на турецком языке кто-то может попытаться обойти механизмы безопасности, блокирующие доступ к URI без учета регистра, которые начинаются с текста «FILE:».Thus, on Turkish systems, someone could circumvent security measures that block access to case-insensitive URIs that begin with "FILE:".

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

В этом случае, поскольку «file:» должен интерпретироваться как нелингвистический идентификатор без учета языка и региональных параметров, нужно писать код, как показано в следующем примере.In this case, because "file:" is meant to be interpreted as a non-linguistic, culture-insensitive identifier, the code should instead be written as shown in the following example.

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

Порядковые операции со строкамиOrdinal String Operations

Указание значения StringComparison.Ordinal или StringComparison.OrdinalIgnoreCase в вызове метода является признаком нелингвистического сравнения, в котором признаки естественного языка игнорируются.Specifying the StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase value in a method call signifies a non-linguistic comparison in which the features of natural languages are ignored. Методы, которые вызываются с этими значениями StringComparison , принимают решения о строковых операциях на основе простых байтовых сравнений, а не таблиц регистров или эквивалентности, которые параметризуются языком и региональными параметрами.Methods that are invoked with these StringComparison values base string operation decisions on simple byte comparisons instead of casing or equivalence tables that are parameterized by culture. В большинстве случаев такой подход наиболее соответствует предполагаемой интерпретации строк, ускоряя выполнение кода и делая его более надежным.In most cases, this approach best fits the intended interpretation of strings while making code faster and more reliable.

Порядковые сравнения — это сравнения строк, в которых каждый байт каждой строки сравнивается без лингвистической интерпретации; например, windows не равно Windows.Ordinal comparisons are string comparisons in which each byte of each string is compared without linguistic interpretation; for example, "windows" does not match "Windows". По сути, это вызов функции strcmp среды выполнения C.This is essentially a call to the C runtime strcmp function. Используйте такое сравнение, когда контекст определяет, что строки должны точно совпадать, или требует использования консервативной политики соответствия.Use this comparison when the context dictates that strings should be matched exactly or demands conservative matching policy. Кроме того, порядковое сравнение — это самая быстрая операция сравнения, потому что при расчете результата не применяются лингвистические правила.Additionally, ordinal comparison is the fastest comparison operation because it applies no linguistic rules when determining a result.

Строки в .NET могут содержать внедренные символы NULL.Strings in .NET can contain embedded null characters. Одним из очевидных различий между порядковым сравнением и сравнением с учетом языка и региональных параметров (включая сравнения, в которых используется инвариантный язык и региональные параметры) является различие в обработке внедренных символов null в строке.One of the clearest differences between ordinal and culture-sensitive comparison (including comparisons that use the invariant culture) concerns the handling of embedded null characters in a string. Эти символы игнорируются при использовании методов String.Compare и String.Equals для сравнений с учетом языка и региональных параметров (включая сравнения, использующие инвариантный язык).These characters are ignored when you use the String.Compare and String.Equals methods to perform culture-sensitive comparisons (including comparisons that use the invariant culture). В результате в сравнениях с учетом языка и региональных параметров строки, содержащие внедренные символы null, считаются равными строкам, которые таких символов не содержат.As a result, in culture-sensitive comparisons, strings that contain embedded null characters can be considered equal to strings that do not.

Важно!

Несмотря на то что в методах сравнения строк не учитываются внедренные символы null, методы поиска строк, такие как String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOfи String.StartsWith , эти символы учитывают.Although string comparison methods disregard embedded null characters, string search methods such as String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOf, and String.StartsWith do not.

В следующем примере выполняется сравнение с учетом языка и региональных параметров строки Aa с аналогичной строкой, содержащей несколько внедренных символов null между А и а, и показано, почему две строки рассматриваются как равные.The following example performs a culture-sensitive comparison of the string "Aa" with a similar string that contains several embedded null characters between "A" and "a", and shows how the two strings are considered equal.

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

Однако строки не считаются равными, если выполняется порядковое сравнение, как показано в следующем примере.However, the strings are not considered equal when you use ordinal comparison, as the following example shows.

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

Порядковые сравнения без учета регистра — это следующий наиболее консервативной подход к решению задачи.Case-insensitive ordinal comparisons are the next most conservative approach. В этих сравнениях почти всегда игнорируется регистр. Так, windows совпадает с Windows.These comparisons ignore most casing; for example, "windows" matches "Windows". При работе с символами ASCII эта политика эквивалентна сравнению StringComparison.Ordinalза исключением того, что стандартные правила регистра ASCII игнорируются.When dealing with ASCII characters, this policy is equivalent to StringComparison.Ordinal, except that it ignores the usual ASCII casing. Следовательно, любой символ в последовательности [A, Z] (\u0041–\u005A) соответствует соответствующему символу в последовательности [a, z] (\u0061–\007A).Therefore, any character in [A, Z] (\u0041-\u005A) matches the corresponding character in [a,z] (\u0061-\007A). Правила регистра за пределами диапазона ASCII используют таблицы инвариантного языка.Casing outside the ASCII range uses the invariant culture's tables. Поэтому следующее сравнениеTherefore, the following comparison:

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

эквивалентно следующему сравнению (но выполняется быстрее):is equivalent to (but faster than) this comparison:

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

Эти сравнения по-прежнему выполняются очень быстро.These comparisons are still very fast.

Примечание

Поведение строки файловой системы, ключей реестра и значений, а также переменные среды лучше всего представлены типом StringComparison.OrdinalIgnoreCase.The string behavior of the file system, registry keys and values, and environment variables is best represented by StringComparison.OrdinalIgnoreCase.

И StringComparison.Ordinal , и StringComparison.OrdinalIgnoreCase используют двоичные значения непосредственно и лучше всего подходят для сопоставления.Both StringComparison.Ordinal and StringComparison.OrdinalIgnoreCase use the binary values directly, and are best suited for matching. При отсутствии точной информации о параметрах сравнения используйте одно из этих двух значений.When you are not sure about your comparison settings, use one of these two values. Однако, поскольку они выполняют побайтовое сравнение, лингвистическая сортировка (как в словаре английского языка) не выполняется, однако используется двоичный порядок сортировки.However, because they perform a byte-by-byte comparison, they do not sort by a linguistic sort order (like an English dictionary) but by a binary sort order. В большинстве случаев для пользователя эти результаты будут выглядеть странно.The results may look odd in most contexts if displayed to users.

Порядковая семантика используется по умолчанию для перегрузок String.Equals , которые не содержат аргумента StringComparison (включая оператор равенства).Ordinal semantics are the default for String.Equals overloads that do not include a StringComparison argument (including the equality operator). В любом случае рекомендуется вызвать перегрузку, содержащую параметр StringComparison .In any case, we recommend that you call an overload that has a StringComparison parameter.

Строковые операции, использующие инвариантный язык и региональные параметрыString Operations that Use the Invariant Culture

В сравнениях с инвариантным языком используется свойство CompareInfo , возвращаемое статическим свойством CultureInfo.InvariantCulture .Comparisons with the invariant culture use the CompareInfo property returned by the static CultureInfo.InvariantCulture property. Это поведение одинаково во всех системах; оно преобразует любые символы за пределами своего диапазона в то, что оно считает эквивалентными инвариантными символами.This behavior is the same on all systems; it translates any characters outside its range into what it believes are equivalent invariant characters. Эта политика может пригодиться для реализации единого набора поведений строк в разных языках и региональных параметрах, однако часто это дает непредвиденные результаты.This policy can be useful for maintaining one set of string behavior across cultures, but it often provides unexpected results.

Сравнения без учета регистра с инвариантным языком также используют статическое свойство CompareInfo , возвращаемое статическим свойством CultureInfo.InvariantCulture для сведений сравнения.Case-insensitive comparisons with the invariant culture use the static CompareInfo property returned by the static CultureInfo.InvariantCulture property for comparison information as well. Любые различия регистров в этих преобразуемых символах игнорируются.Any case differences among these translated characters are ignored.

Сравнения с использованием StringComparison.InvariantCulture и StringComparison.Ordinal работают одинаково в строках ASCII.Comparisons that use StringComparison.InvariantCulture and StringComparison.Ordinal work identically on ASCII strings. Однако StringComparison.InvariantCulture принимает лингвистические решения, которые могут не подходить для строк, которые нужно интерпретировать как набор байтов.However, StringComparison.InvariantCulture makes linguistic decisions that might not be appropriate for strings that have to be interpreted as a set of bytes. Объект CultureInfo.InvariantCulture.CompareInfo заставляет метод Compare интерпретировать определенные наборы символов как эквивалентные.The CultureInfo.InvariantCulture.CompareInfo object makes the Compare method interpret certain sets of characters as equivalent. Например, следующие элементы эквивалентны только в инвариантном языке.For example, the following equivalence is valid under the invariant culture:

InvariantCulture: a + ̊ = åInvariantCulture: a + ̊ = å

Латинская строчная буква А, а (\u0061), расположенная рядом с объединяющим кольцом над символом "+ " ̊"(\u030a), интерпретируется как строчная латинская буква а с кольцом над ней, å (\u00e5).The LATIN SMALL LETTER A character "a" (\u0061), when it is next to the COMBINING RING ABOVE character "+ " ̊" (\u030a), is interpreted as the LATIN SMALL LETTER A WITH RING ABOVE character "å" (\u00e5). Как показано в следующем примере, это поведение отличается от порядкового сравнения.As the following example shows, this behavior differs from ordinal comparison.

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

При интерпретации имен файлов, файлов cookie или чего-либо еще, где могут появляться такие сочетания, как å, порядковые сравнения по-прежнему являются наиболее понятным и подходящим поведением.When interpreting file names, cookies, or anything else where a combination such as "å" can appear, ordinal comparisons still offer the most transparent and fitting behavior.

В целом в инвариантном языке очень мало свойств, которые могли бы сделать его полезным для сравнения.On balance, the invariant culture has very few properties that make it useful for comparison. Он выполняет сравнения с учетом лингвистических параметров, что не позволяет гарантировать полную эквивалентность символов, однако не подходит для отображения на любом языке.It does comparison in a linguistically relevant manner, which prevents it from guaranteeing full symbolic equivalence, but it is not the choice for display in any culture. Одной из оснований использования StringComparison.InvariantCulture для сравнения является необходимость сохранить упорядоченные данные для идентичного отображения на разных языках.One of the few reasons to use StringComparison.InvariantCulture for comparison is to persist ordered data for a cross-culturally identical display. Например, если к приложению прилагается крупный файл данных, содержащий список сортированных идентификаторов для отображения, добавление в этот список потребует вставки элементов с сортировкой в инвариантном стиле.For example, if a large data file that contains a list of sorted identifiers for display accompanies an application, adding to this list would require an insertion with invariant-style sorting.

К началуBack to top

Выбор элемента StringComparison для своего вызова методаChoosing a StringComparison Member for Your Method Call

В следующей таблице приведено сопоставление семантического контекста строк элементу перечисления StringComparison .The following table outlines the mapping from semantic string context to a StringComparison enumeration member.

ДанныеData ПоведениеBehavior Соответствующее сравнение System.StringComparisonCorresponding System.StringComparison

valuevalue
Внутренние идентификаторы с учетом регистраCase-sensitive internal identifiers.

Идентификаторы с учетом регистра в таких стандартах, как XML и HTTP.Case-sensitive identifiers in standards such as XML and HTTP.

Параметры безопасности с учетом регистра.Case-sensitive security-related settings.
Нелингвистические идентификаторы с точным соответствием байтов.A non-linguistic identifier, where bytes match exactly. Ordinal
Внутренние идентификаторы без учета регистра.Case-insensitive internal identifiers.

Идентификаторы без учета регистра в таких стандартах, как XML и HTTP.Case-insensitive identifiers in standards such as XML and HTTP.

Пути к файлам.File paths.

Ключи реестра и значения.Registry keys and values.

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

Идентификаторы ресурсов (например, имена дескрипторов).Resource identifiers (for example, handle names).

Параметры безопасности без учета регистра.Case-insensitive security-related settings.
Нелингвистический идентификатор, в котором регистр не учитывается; особенно данные, хранящиеся в большинстве системных служб Windows.A non-linguistic identifier, where case is irrelevant; especially data stored in most Windows system services. OrdinalIgnoreCase
Некоторые сохраненные лингвистически релевантные данные.Some persisted, linguistically relevant data.

Отображение лингвистических данных, требующее фиксированного порядка сортировки.Display of linguistic data that requires a fixed sort order.
Лингвистически релевантные данные без учета языка и региональных параметров.Culturally agnostic data that still is linguistically relevant. InvariantCulture

-или--or-

InvariantCultureIgnoreCase
Данные, отображаемые пользователю.Data displayed to the user.

Пользовательский ввод в большинстве случаев.Most user input.
Данные, требующие местных лингвистических правил.Data that requires local linguistic customs. CurrentCulture

-или--or-

CurrentCultureIgnoreCase

К началуBack to top

Распространенные методы сравнения строк в .NETCommon String Comparison Methods in .NET

В следующих разделах описываются методы, которые чаще всего используются для сравнения строк.The following sections describe the methods that are most commonly used for string comparison.

String.CompareString.Compare

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

Поскольку эта операция наиболее тесно связана с интерпретацией строк, необходимо изучить все экземпляры вызовов этого метода, чтобы определить, должны ли строки интерпретироваться с учетом текущего языка и региональных параметров, либо их нужно (символически) отделить от языка и региональных параметров.As the operation most central to string interpretation, all instances of these method calls should be examined to determine whether strings should be interpreted according to the current culture, or dissociated from the culture (symbolically). Обычно выбирается последнее, и тогда должно использоваться сравнение StringComparison.Ordinal .Typically, it is the latter, and a StringComparison.Ordinal comparison should be used instead.

Класс System.Globalization.CompareInfo , возвращаемый свойством CultureInfo.CompareInfo , также включает метод Compare , предоставляющий большое количество соответствующих параметров (порядковый, игнорирование пробела, игнорирование типа каны и т. д.) посредством перечисления флага CompareOptions .The System.Globalization.CompareInfo class, which is returned by the CultureInfo.CompareInfo property, also includes a Compare method that provides a large number of matching options (ordinal, ignoring white space, ignoring kana type, and so on) by means of the CompareOptions flag enumeration.

String.CompareToString.CompareTo

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

В настоящее время этот метод не предлагает перегрузку, задающую тип StringComparison .This method does not currently offer an overload that specifies a StringComparison type. Обычно возможно преобразовать этот метод в рекомендованную форму String.Compare(String, String, StringComparison) .It is usually possible to convert this method to the recommended String.Compare(String, String, StringComparison) form.

Типы, реализующие интерфейсы IComparable и IComparable<T> , реализуют этот метод.Types that implement the IComparable and IComparable<T> interfaces implement this method. Поскольку не предлагается параметр StringComparison , реализация типов часто позволяет пользователю задать StringComparer в своем конструкторе.Because it does not offer the option of a StringComparison parameter, implementing types often let the user specify a StringComparer in their constructor. В следующем примере определяется класс FileName , конструктор класса которого включает параметр StringComparer .The following example defines a FileName class whose class constructor includes a StringComparer parameter. Затем этот объект StringComparer используется в методе FileName.CompareTo .This StringComparer object is then used in the FileName.CompareTo method.

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

String.EqualsString.Equals

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

Класс String позволяет выполнить проверку на равенство, вызвав статические перегрузки метода Equals или перегрузки экземпляров. Кроме того, можно воспользоваться оператором статического равенства.The String class lets you test for equality by calling either the static or instance Equals method overloads, or by using the static equality operator. Перегрузки и оператор используют порядковое сравнение по умолчанию.The overloads and operator use ordinal comparison by default. Однако рекомендуется вызывать перегрузку, которая явно задает тип StringComparison , даже если требуется выполнить порядковое сравнение. Это упрощает поиск кода для интерпретации определенной строки.However, we still recommend that you call an overload that explicitly specifies the StringComparison type even if you want to perform an ordinal comparison; this makes it easier to search code for a certain string interpretation.

String.ToUpper и String.ToLowerString.ToUpper and String.ToLower

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

Следует соблюдать осторожность, используя эти методы, поскольку принудительное преобразование строки в нижний или верхний регистр часто используется в качестве незначительной нормализации для сравнения строк независимо от регистра.You should be careful when you use these methods, because forcing a string to a uppercase or lowercase is often used as a small normalization for comparing strings regardless of case. В этом случае целесообразно выполнить сравнение без учета регистра.If so, consider using a case-insensitive comparison.

Также доступны методы String.ToUpperInvariant и String.ToLowerInvariant .The String.ToUpperInvariant and String.ToLowerInvariant methods are also available. ToUpperInvariant — это стандартный способ нормализации регистра.ToUpperInvariant is the standard way to normalize case. Сравнения, выполненные с помощью StringComparison.OrdinalIgnoreCase , с точки зрения поведения представляют собой комбинацию из двух вызовов: вызов ToUpperInvariant в обоих аргументах строки и выполнение сравнения с использованием StringComparison.Ordinal.Comparisons made using StringComparison.OrdinalIgnoreCase are behaviorally the composition of two calls: calling ToUpperInvariant on both string arguments, and doing a comparison using StringComparison.Ordinal.

Также доступны перегрузки для преобразования в верхний и нижний регистр в конкретном языке. Для этого передается объект CultureInfo , представляющий этот язык для метода.Overloads are also available for converting to uppercase and lowercase in a specific culture, by passing a CultureInfo object that represents that culture to the method.

Char.ToUpper и Char.ToLowerChar.ToUpper and Char.ToLower

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

Эти методы работают аналогично методам String.ToUpper и String.ToLower , описанным в предыдущем разделе.These methods work similarly to the String.ToUpper and String.ToLower methods described in the previous section.

String.StartsWith и String.EndsWithString.StartsWith and String.EndsWith

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

По умолчанию оба этих метода выполняют сравнение с учетом языка и региональных параметров.By default, both of these methods perform a culture-sensitive comparison.

String.IndexOf и String.LastIndexOfString.IndexOf and String.LastIndexOf

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

Перегрузки этих методов по умолчанию выполняют сравнения непоследовательно.There is a lack of consistency in how the default overloads of these methods perform comparisons. Все методы String.IndexOf и String.LastIndexOf , включающие параметр Char , выполняют порядковое сравнение, однако методы String.IndexOf и String.LastIndexOf по умолчанию, которые включают параметр String , выполняют сравнение с учетом языка и региональных параметров.All String.IndexOf and String.LastIndexOf methods that include a Char parameter perform an ordinal comparison, but the default String.IndexOf and String.LastIndexOf methods that include a String parameter perform a culture-sensitive comparison.

Если нужно вызвать метод String.IndexOf(String) или String.LastIndexOf(String) и передать ему строку для определения ее местоположения в текущем экземпляре, рекомендуется вызвать перегрузку, которая явно задает тип StringComparison .If you call the String.IndexOf(String) or String.LastIndexOf(String) method and pass it a string to locate in the current instance, we recommend that you call an overload that explicitly specifies the StringComparison type. Перегрузки, включающие аргумент Char , не позволяют задать тип StringComparison .The overloads that include a Char argument do not allow you to specify a StringComparison type.

К началуBack to top

Методы, выполняющие сравнение строк опосредованноMethods that Perform String Comparison Indirectly

Некоторые нестроковые методы, основным назначением которых является сравнение строк, используют тип StringComparer .Some non-string methods that have string comparison as a central operation use the StringComparer type. Класс StringComparer включает шесть статических свойств, возвращающих экземпляры StringComparer , методы StringComparer.Compare которых выполняют следующие типы сравнения строк.The StringComparer class includes six static properties that return StringComparer instances whose StringComparer.Compare methods perform the following types of string comparisons:

Array.Sort и Array.BinarySearchArray.Sort and Array.BinarySearch

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

При хранении любых данных в коллекции или считывании сохраненных данных из файла или базы данных в коллекцию изменение текущего языка и региональных параметров может сделать недействительными инварианты этой коллекции.When you store any data in a collection, or read persisted data from a file or database into a collection, switching the current culture can invalidate the invariants in the collection. Метод Array.BinarySearch предполагает, что элементы в массиве, поиск которых необходимо выполнить, уже сортированы.The Array.BinarySearch method assumes that the elements in the array to be searched are already sorted. Чтобы отсортировать любой стоковый элемент в массиве метод Array.Sort вызывает метод String.Compare для упорядочивания отдельных элементов.To sort any string element in the array, the Array.Sort method calls the String.Compare method to order individual elements. Использовать средство сравнения с учетом языка и региональных параметров может быть опасно, если язык и региональные параметры изменяются в период между сортировкой массива и поиском в содержимом этого массива.Using a culture-sensitive comparer can be dangerous if the culture changes between the time that the array is sorted and its contents are searched. Например, в следующем коде для хранения и извлечения данных используется средство сравнения, которое неявно предоставляется свойством Thread.CurrentThread.CurrentCulture .For example, in the following code, storage and retrieval operate on the comparer that is provided implicitly by the Thread.CurrentThread.CurrentCulture property. Если язык и региональные параметры могут измениться между вызовом StoreNames и DoesNameExistи особенно если содержимое массива сохраняется в период между вызовами этих двух методов, двоичный поиск может завершиться ошибкой.If the culture can change between the calls to StoreNames and DoesNameExist, and especially if the array contents are persisted somewhere between the two method calls, the binary search may fail.

// 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.
}
' 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

Рекомендуемый вариант показан в следующем примере, где один и тот же метод порядкового сравнения (без учета языка и региональных параметров) используется для сортировки массива и поиска в нем.A recommended variation appears in the following example, which uses the same ordinal (culture-insensitive) comparison method both to sort and to search the array. Измененный код отражается в строках, помеченных в этих двух примерах как Line A и Line B .The change code is reflected in the lines labeled Line A and Line B in the two examples.

// 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.
}
' 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

Если эти данные сохраняются и перемещаются в разных языках, а для представления этих данных пользователю используется сортировка, целесообразно использовать StringComparison.InvariantCulture, действующий с учетом лингвистических правил и, следовательно, повышающий качество выводимых пользователю данных, но при этом не подверженный влиянию изменений в языке и региональных параметрах.If this data is persisted and moved across cultures, and sorting is used to present this data to the user, you might consider using StringComparison.InvariantCulture, which operates linguistically for better user output but is unaffected by changes in culture. В следующем примере два предыдущих примера изменяются так, чтобы для сортировки массива и поиска в нем использовался инвариантный язык.The following example modifies the two previous examples to use the invariant culture for sorting and searching the array.

// 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.
}
' 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

Пример коллекций: конструктор хэш-таблицыCollections Example: Hashtable Constructor

Хэширование строк — это второй пример операции, на которую влияет способ сравнения строк.Hashing strings provides a second example of an operation that is affected by the way in which strings are compared.

В следующем примере создается экземпляр объекта Hashtable путем передачи его объекту StringComparer , который возвращается свойством StringComparer.OrdinalIgnoreCase .The following example instantiates a Hashtable object by passing it the StringComparer object that is returned by the StringComparer.OrdinalIgnoreCase property. Поскольку класс StringComparer , который является производным от StringComparer , реализует интерфейс IEqualityComparer , его метод GetHashCode используется для вычисления хэш-кода строк в хэш-таблице.Because a class StringComparer that is derived from StringComparer implements the IEqualityComparer interface, its GetHashCode method is used to compute the hash code of strings in the hash table.

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

К началуBack to top

Отображение и сохранение форматированных данныхDisplaying and Persisting Formatted Data

При отображении нестроковых данных, например чисел, дат и времени, пользователям следует форматировать их с использованием параметров языка и региональных параметров пользователя.When you display non-string data such as numbers and dates and times to users, format them by using the user's cultural settings. По умолчанию все следующие объекты используют текущий язык и региональные параметры потока в операциях форматирования:By default, the following all use the current thread culture in formatting operations:

  • Интерполированные строки, поддерживаемые компиляторами C# и Visual Basic.Interpolated strings supported by the C# and Visual Basic compilers.

  • Строковые операции объединения, использующие операторы объединения C# или Visual Basic или вызывающие метод String.Concat напрямую.String concatenation operations that use the C# or Visual Basic concatenation operators or that call the String.Concat method directly.

  • метод String.Format ;The String.Format method.

  • Методы ToString числовых типов, а также типы даты и времени.The ToString methods of the numeric types and the date and time types.

Чтобы явно указать, что строка должна форматироваться с помощью правил заданного языка и региональных параметров или инвариантного языка и региональных параметров, можно сделать следующее:To explicitly specify that a string should be formatted by using the conventions of a designated culture or the invariant culture, you can do the following:

  • При использовании методов String.Format и ToString вызовите перегрузку с параметром provider, например String.Format(IFormatProvider, String, Object[]) или DateTime.ToString(IFormatProvider), и передайте ему свойство CultureInfo.CurrentCulture, экземпляр CultureInfo, представляющий требуемый язык и региональные параметры, или свойство CultureInfo.InvariantCulture.When using the String.Format and ToString methods, call an overload that has a provider parameter, such as String.Format(IFormatProvider, String, Object[]) or DateTime.ToString(IFormatProvider), and pass it the CultureInfo.CurrentCulture property, a CultureInfo instance that represents the desired culture, or the CultureInfo.InvariantCulture property.

  • Для объединения строк не позволяйте компилятору выполнять неявные преобразования.For string concatenation, do not allow the compiler to perform any implicit conversions. Вместо этого выполните явное преобразование путем вызова перегрузки ToString с параметром provider.Instead, perform an explicit conversion by calling a ToString overload that has a provider parameter. Например, компилятор неявно использует текущий язык и региональные параметры при преобразовании значения Double в строку в следующем коде C#:For example, the compiler implicitly uses the current culture when converting a Double value to a string in the following C# code:

    string concat1 = "The amount is " + 126.03 + ".";
    Console.WriteLine(concat1);
    

    Вместо этого можно явно указать язык и региональные параметры, соглашения о форматировании которых используются в преобразовании, вызвав метод Double.ToString(IFormatProvider), как показано в следующем коде C#:Instead, you can explicitly specify the culture whose formatting conventions are used in the conversion by calling the Double.ToString(IFormatProvider) method, as the following C# code does:

    string concat2 = "The amount is " + 126.03.ToString(CultureInfo.InvariantCulture) + ".";
    Console.WriteLine(concat2);
    
  • Для интерполяции строк вместо назначения интерполированной строки экземпляру String назначьте его FormattableString.For string interpolation, rather than assigning an interpolated string to a String instance, assign it to a FormattableString. Затем можно вызвать его метод FormattableString.ToString(), чтобы создать результирующую строку, отражающую правила для текущего языка и региональных параметров, либо можно вызвать метод FormattableString.ToString(IFormatProvider), чтобы создать результирующую строку, отражающую правила для указанного языка и региональных параметров.You can then call its FormattableString.ToString() method produce a result string that reflects the conventions of the current culture, or you can call the FormattableString.ToString(IFormatProvider) method to produce a result string that reflects the conventions of a specified culture. Также можно передать форматируемую строку статическому методу FormattableString.Invariant, чтобы создать результирующую строку, отражающую правила для инвариантного языка и региональных параметров.You can also pass the formattable string to the static FormattableString.Invariant method to produce a result string that reflects the conventions of the invariant culture. Этот подход показан в приведенном ниже примере.The following example illustrates this approach. (Выходные данные примера отражают текущий язык и региональные параметры en-US.)(The output from the example reflects a current culture of en-US.)

    using System;
    using System.Globalization;
    
    class Program
    {
        static void Main()
        {
            Decimal value = 126.03m;
            FormattableString amount = $"The amount is {value:C}"; 
            Console.WriteLine(amount.ToString());
            Console.WriteLine(amount.ToString(new CultureInfo("fr-FR")));
            Console.WriteLine(FormattableString.Invariant(amount));
        }
    }
    // The example displays the following output:
    //    The amount is $126.03
    //    The amount is 126,03 €
    //    The amount is ¤126.03
    
    Imports System.Globalization
    
    Module Program
        Sub Main()
            Dim value As Decimal = 126.03
            Dim amount As FormattableString = $"The amount is {value:C}" 
            Console.WriteLine(amount.ToString())
            Console.WriteLine(amount.ToString(new CultureInfo("fr-FR")))
            Console.WriteLine(FormattableString.Invariant(amount))
        End Sub
    End Module
    ' The example displays the following output:
    '    The amount is $126.03
    '    The amount is 126,03 €
    '    The amount is ¤126.03
    

Нестроковые данные можно сохранить в виде двоичных или форматированных данных.You can persist non-string data either as binary data or as formatted data. Если решено сохранять данные в виде форматированных, нужно вызвать перегрузку метода форматирования, которая включает параметр provider , и передать ей свойство CultureInfo.InvariantCulture .If you choose to save it as formatted data, you should call a formatting method overload that includes a provider parameter and pass it the CultureInfo.InvariantCulture property. Инвариантный язык и региональные параметры предоставляют согласованный формат для форматированных данных независимо от языка, региональных параметров и компьютера.The invariant culture provides a consistent format for formatted data that is independent of culture and machine. Напротив, сохранение форматированных данных с использованием языков и региональных параметров, отличающихся от инвариантных, имеет ряд ограничений.In contrast, persisting data that is formatted by using cultures other than the invariant culture has a number of limitations:

  • Данные, вероятно, станут недоступными для использования после извлечения в системе с другим языком либо если пользователь текущей системы сменит текущий язык и попытается извлечь данные.The data is likely to be unusable if it is retrieved on a system that has a different culture, or if the user of the current system changes the current culture and tries to retrieve the data.

  • Свойства языка и региональных параметров на конкретном компьютере могут отличаться от стандартных значений.The properties of a culture on a specific computer can differ from standard values. Пользователь может в любой момент настроить параметры отображения с учетом языка и региональных параметров.At any time, a user can customize culture-sensitive display settings. По этой причине форматированные данные, которые сохраняются в системе, могут стать недоступными для чтения после изменения настроек языка пользователем.Because of this, formatted data that is saved on a system may not be readable after the user customizes cultural settings. Переносимость форматированных данных с одного компьютера на другой, вероятно, будет еще более ограниченной.The portability of formatted data across computers is likely to be even more limited.

  • Международные, региональные и национальные стандарты, регулирующие форматирование чисел, дат и времени, со временем меняются, и эти изменения отражаются в обновлениях ОС Windows.International, regional, or national standards that govern the formatting of numbers or dates and times change over time, and these changes are incorporated into Windows operating system updates. При изменении правил форматирования данные, форматированные с использованием старых правил, становятся недоступными для чтения.When formatting conventions change, data that was formatted by using the previous conventions may become unreadable.

В следующем примере демонстрируется ограниченная переносимость в результате использования для сохранения данных форматирования с учетом языка и региональных параметров.The following example illustrates the limited portability that results from using culture-sensitive formatting to persist data. В этом примере массив значений даты и времени сохраняется в файл.The example saves an array of date and time values to a file. Данные форматируются с использованием правил английского языка (США).These are formatted by using the conventions of the English (United States) culture. После того как приложение сменит текущий язык потока на французский (Швейцария), оно попытается прочитать сохраненные значения, используя правила форматирования текущей культуры.After the application changes the current thread culture to French (Switzerland), it tries to read the saved values by using the formatting conventions of the current culture. При попытке чтения двух элементов данных создается исключение FormatException , а массив дат теперь содержит два неправильных элемента, равных MinValue.The attempt to read two of the data items throws a FormatException exception, and the array of dates now contains two incorrect elements that are equal to MinValue.

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

public class Example
{
   private static string filename = @".\dates.dat";

   public static void Main()
   {
      DateTime[] dates = { new DateTime(1758, 5, 6, 21, 26, 0), 
                           new DateTime(1818, 5, 5, 7, 19, 0), 
                           new DateTime(1870, 4, 22, 23, 54, 0),  
                           new DateTime(1890, 9, 8, 6, 47, 0), 
                           new DateTime(1905, 2, 18, 15, 12, 0) }; 
      // Write the data to a file using the current culture.
      WriteData(dates);
      // Change the current culture.
      Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-CH");
      // Read the data using the current culture.
      DateTime[] newDates = ReadData();
      foreach (var newDate in newDates)
         Console.WriteLine(newDate.ToString("g"));
   }
   
   private static void WriteData(DateTime[] dates) 
   {
      StreamWriter sw = new StreamWriter(filename, false, Encoding.UTF8);    
      for (int ctr = 0; ctr < dates.Length; ctr++) {
         sw.Write("{0}", dates[ctr].ToString("g", CultureInfo.CurrentCulture));
         if (ctr < dates.Length - 1) sw.Write("|");   
      }      
      sw.Close();
   }
   
   private static DateTime[] ReadData() 
   {
      bool exceptionOccurred = false;
           
      // Read file contents as a single string, then split it.
      StreamReader sr = new StreamReader(filename, Encoding.UTF8);
      string output = sr.ReadToEnd();
      sr.Close();   

      string[] values = output.Split( new char[] { '|' } );
      DateTime[] newDates = new DateTime[values.Length]; 
      for (int ctr = 0; ctr < values.Length; ctr++) {
         try {
            newDates[ctr] = DateTime.Parse(values[ctr], CultureInfo.CurrentCulture);
         }
         catch (FormatException) {
            Console.WriteLine("Failed to parse {0}", values[ctr]);
            exceptionOccurred = true;
         }
      }      
      if (exceptionOccurred) Console.WriteLine();
      return newDates;
   }
}
// The example displays the following output:
//       Failed to parse 4/22/1870 11:54 PM
//       Failed to parse 2/18/1905 3:12 PM
//       
//       05.06.1758 21:26
//       05.05.1818 07:19
//       01.01.0001 00:00
//       09.08.1890 06:47
//       01.01.0001 00:00
//       01.01.0001 00:00
Imports System.Globalization
Imports System.IO
Imports System.Text
Imports System.Threading

Module Example
   Private filename As String = ".\dates.dat"
   
   Public Sub Main()
      Dim dates() As Date = { #5/6/1758 9:26PM#, #5/5/1818 7:19AM#, _ 
                              #4/22/1870 11:54PM#, #9/8/1890 6:47AM#, _ 
                              #2/18/1905 3:12PM# }
      ' Write the data to a file using the current culture.
      WriteData(dates)
      ' Change the current culture.
      Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-CH")
      ' Read the data using the current culture.
      Dim newDates() As Date = ReadData()
      For Each newDate In newDates
         Console.WriteLine(newDate.ToString("g"))
      Next
   End Sub
   
   Private Sub WriteData(dates() As Date)
      Dim sw As New StreamWriter(filename, False, Encoding.Utf8)    
      For ctr As Integer = 0 To dates.Length - 1
         sw.Write("{0}", dates(ctr).ToString("g", CultureInfo.CurrentCulture))
         If ctr < dates.Length - 1 Then sw.Write("|")   
      Next      
      sw.Close()
   End Sub
   
   Private Function ReadData() As Date()
      Dim exceptionOccurred As Boolean = False
           
      ' Read file contents as a single string, then split it.
      Dim sr As New StreamReader(filename, Encoding.Utf8)
      Dim output As String = sr.ReadToEnd()
      sr.Close()   

      Dim values() As String = output.Split( {"|"c } )
      Dim newDates(values.Length - 1) As Date 
      For ctr As Integer = 0 To values.Length - 1
         Try
            newDates(ctr) = DateTime.Parse(values(ctr), CultureInfo.CurrentCulture)
         Catch e As FormatException
            Console.WriteLine("Failed to parse {0}", values(ctr))
            exceptionOccurred = True
         End Try
      Next      
      If exceptionOccurred Then Console.WriteLine()
      Return newDates
   End Function
End Module
' The example displays the following output:
'       Failed to parse 4/22/1870 11:54 PM
'       Failed to parse 2/18/1905 3:12 PM
'       
'       05.06.1758 21:26
'       05.05.1818 07:19
'       01.01.0001 00:00
'       09.08.1890 06:47
'       01.01.0001 00:00
'       01.01.0001 00:00
'

Однако если заменить свойство CultureInfo.CurrentCulture на CultureInfo.InvariantCulture в вызовах методов DateTime.ToString(String, IFormatProvider) и DateTime.Parse(String, IFormatProvider), хранимые данные даты и времени восстанавливаются успешно, как показано ниже.However, if you replace the CultureInfo.CurrentCulture property with CultureInfo.InvariantCulture in the calls to DateTime.ToString(String, IFormatProvider) and DateTime.Parse(String, IFormatProvider), the persisted date and time data is successfully restored, as the following output shows.

06.05.1758 21:26  
05.05.1818 07:19  
22.04.1870 23:54  
08.09.1890 06:47  
18.02.1905 15:12  

См. такжеSee also