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

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

 

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

В этом разделе рассматриваются методы сортировки, сравнения строк и использования прописных и строчных букв в .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 для проверки равенства двух строк.

  • Используйте методы String.Compare и String.CompareTo для сортировки строк, но не для проверки их равенства.

  • Используйте форматирование с учетом языка и региональных параметров для отображения нестроковых данных, например чисел и дат, в пользовательском интерфейсе. Для сохранения нестроковых данных в строковой форме используйте форматирование инвариантного языка и региональных параметров.

При использовании строк избегайте следующих действий.

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

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

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

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

К началу

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

Элемент StringComparison

Описание

CurrentCulture 

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

CurrentCultureIgnoreCase 

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

InvariantCulture 

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

InvariantCultureIgnoreCase 

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

Ordinal 

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

OrdinalIgnoreCase 

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

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

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

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

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

    string protocol = GetProtocol(url);       
    if (String.Equals(protocol, "http")) {
       // ...Code to handle HTTP protocol.
    }
    else {
       throw new InvalidOperationException();
    }
    

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

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 показаны в разных местах массива сортированных строк.

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 без учета регистра.

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 static bool IsFileURI(String path) 
{
   return path.StartsWith("FILE:", true, null);
}

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

public static bool IsFileURI(string path) 
{
   return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
}

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

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

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

System_CAPS_importantВажно

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

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

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

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

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

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

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

Эти сравнения по-прежнему выполняются очень быстро.

System_CAPS_noteПримечание

Поведение строки файловой системы, ключей реестра и значений, а также переменные среды лучше всего представлены типом 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 + ̊ = å

Латинская строчная буква А, а (\u0061), находясь рядом с объединяющим кольцом над символом «+» , « ̊» (\u030a), интерпретируется как строчная латинская буква а с кольцом над ней, å (\u00e5). Как показано в следующем примере, это поведение отличается от порядкового сравнения.

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.

Данные

Назначение

Соответствующее сравнение System.StringComparison

Значение

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

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

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

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

Ordinal

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

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

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

Ключи реестра и значения.

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

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

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

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

OrdinalIgnoreCase 

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

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

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

InvariantCulture  

-или-

InvariantCultureIgnoreCase 

Данные, отображаемые пользователю.

Пользовательский ввод в большинстве случаев.

Данные, требующие местных лингвистических правил.

CurrentCulture

-или-

CurrentCultureIgnoreCase 

К началу

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Интерпретация по умолчанию: 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.

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

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

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

К началу

При отображении нестроковых данных, например чисел, дат и времени, пользователям следует форматировать их с использованием параметров языка и региональных параметров пользователя. По умолчанию метод String.Format и методы ToString числовых типов и типов даты и времени используют текущий язык потока для операций форматирования. Чтобы явно указать, что метод форматирования должен использовать текущий язык и региональные параметры, можно вызвать перегрузку метода форматирования, который имеет параметр provider, например String.Format(IFormatProvider, String, Object[]) или DateTime.ToString(IFormatProvider), и передать ему свойство CultureInfo.CurrentCulture.

Нестроковые данные можно сохранить в виде двоичных или форматированных данных. Если решено сохранять данные в виде форматированных, нужно вызвать перегрузку метода форматирования, которая включает параметр provider, и передать ей свойство CultureInfo.InvariantCulture. Инвариантный язык и региональные параметры предоставляют согласованный формат для форматированных данных независимо от языка, региональных параметров и компьютера. Напротив, сохранение форматированных данных с использованием языков и региональных параметров, отличающихся от инвариантных, имеет ряд ограничений.

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

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

  • Международные, региональные и национальные стандарты, регулирующие форматирование чисел, дат и времени, со временем меняются, и эти изменения отражаются в обновлениях ОС Windows. При изменении правил форматирования данные, форматированные с использованием старых правил, становятся недоступными для чтения.

В следующем примере демонстрируется ограниченная переносимость в результате использования для сохранения данных форматирования с учетом языка и региональных параметров. В этом примере массив значений даты и времени сохраняется в файл. Данные форматируются с использованием правил английского языка (США). После того как приложение сменит текущий язык потока на французский (Швейцария), оно попытается прочитать сохраненные значения, используя правила форматирования текущей культуры. При попытке чтения двух элементов данных создается исключение FormatException, а массив дат теперь содержит два неправильных элемента, равных 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

Однако если заменить свойство CultureInfo.CurrentCulture на CultureInfo.InvariantCulture в вызовах методов DateTime.ToString(String, IFormatProvider) и DateTime.Parse(String, IFormatProvider), хранимые данные даты и времени восстанавливаются успешно, как показано ниже.


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

Показ: