Рекомендации по использованию строк в .NET Framework
Платформа .NET Framework предоставляет расширенную поддержку разработки локализованных и глобализованных приложений, а также упрощает применение правил текущего или указанного языка и региональных параметров при выполнении таких распространенных операций, как сортировка строк и их отображение. Но операция по сортировке или отображению строк не всегда зависит от языка и региональных параметров. Например, строки, предназначенные для внутреннего использования приложением, обычно обрабатываются одинаково для всех языков и региональных параметров. Если независимые от языка и региональных параметров строковые данные, такие как XML-теги, HTML-теги, имена пользователей, пути файлов и имена системных объектов, интерпретируются как зависимые от языка и региональных параметров, то это может привести к трудно выявляемым ошибкам кода приложения, уменьшению производительности и в некоторых случаях к проблемам с безопасностью.
В данной теме рассматриваются методы сортировки, сравнения и использования прописных и строчных букв в .NET Framework 4 и последующих версиях, даются рекомендации по выбору подходящего метода обработки строк и предоставляются дополнительные сведения об этих методах.
В этом разделе содержатся следующие подразделы.
Рекомендации по использованию строк
Явное задание сравнений строк
Подробные сведения по сравнению строк
Выбор элемента StringComparison для вызова метода
Наиболее распространенные методы сравнения строк в .NET Framework
Методы неявного сравнения строк
Рекомендации по использованию строк
При разработке на платформе .NET Framework необходимо следовать приведенным далее рекомендациям по использованию строк.
Следует использовать перегрузки, которые явно задают правила сравнения строк для операций со строками. Обычно это подразумевает вызов перегрузки метода с параметром типа StringComparison.
В качестве безопасных значений по умолчанию для независимого от языка и региональных параметров сопоставления строк следует использовать для сравнений StringComparison.Ordinal или StringComparison.OrdinalIgnoreCase.
Для повышения производительности следует использовать сравнения с StringComparison.Ordinal или StringComparison.OrdinalIgnoreCase.
При отображении выходных результатов для пользователя следует применять операции со строками на основе StringComparison.CurrentCulture.
Для сравнения не лингвистических данных (например символьных) следует использовать не лингвистические значения StringComparison.Ordinal или StringComparison.OrdinalIgnoreCase вместо операций со строками на основе CultureInfo.InvariantCulture.
При нормализации строк для сравнения следует использовать метод String.ToUpperInvariant вместо метода String.ToLowerInvariant.
Для проверки, являются ли две строки равными, следует использовать перегрузку метода String.Equals.
Методы Compare и CompareTo следует использовать для сортировки строк, а не для проверки на равенство.
При использовании строк следует избегать применения приведенных далее методик.
Не следует использовать перегрузки, которые не задают явно или неявно правила сравнения строк для операций со строками.
В большинстве случаев не следует использовать операции со строками на основе StringComparison.InvariantCulture. Одно из немногих исключений – случай, когда сохраняются лингвистически значимые, но независимые от языка и региональных параметров данные.
Не следует использовать перегрузку метода String.Compare или CompareTo и проверку, равно ли возвращаемое значение 0, для определения равенства двух строк.
К началу
Явное задание сравнений строк
Большинство методов обработки строк платформы .NET Framework являются перегруженными. Обычно одни перегрузки принимают параметры по умолчанию, а другие не принимают и вместо перегрузок определяют точный путь сравнения или обработки строк. Большинство методов, не принимающих параметры по умолчанию, включают параметр типа StringComparison, являющийся перечислением, явно задающим правила сравнения строк в зависимости от языка и региональных параметров, а также регистра. В следующей таблице приводятся элементы перечисления StringComparison и их описания.
Элемент StringComparison |
Описание |
---|---|
Выполняет сравнение с учетом регистра и с использованием текущего языка и региональных параметров. |
|
Выполняет сравнение без учета регистра и с использованием текущего языка и региональных параметров. |
|
Выполняет сравнение с учетом регистра и с использованием инвариантного языка и региональных параметров. |
|
Выполняет сравнение без учета регистра и с использованием инвариантного языка и региональных параметров. |
|
Выполняет порядковое сравнение. |
|
Выполняет порядковое сравнение без учета регистра. |
Например, метод IndexOf, возвращающий индекс части строки в объекте String, соответствующей символу или строке, имеет девять перегрузок:
IndexOf(Char), IndexOf(Char, Int32) и IndexOf(Char, Int32, Int32), которые по умолчанию выполняют порядковый (с учетом регистра и без учета языка и региональных параметров) поиск символа в строке;
IndexOf(String), IndexOf(String, Int32) и IndexOf(String, Int32, Int32), которые по умолчанию выполняют поиск части строки с учетом регистра и с учетом языка и региональных параметров;
IndexOf(String, StringComparison), IndexOf(String, Int32, StringComparison) и IndexOf(String, Int32, Int32, StringComparison), которые включают параметр типа StringComparison, позволяющий указывать форму сравнения.
Рекомендуется выбирать перегрузку, не использующую значения по умолчанию, по приведенным далее причинам.
Некоторые перегрузки с параметрами по умолчанию (те, которые выполняют поиск Char в экземпляре строки) выполняют порядковое сравнение, в то время как другие (те, которые ищут строку в экземпляре строки) учитывают язык и региональные параметры. Поскольку трудно запомнить, какой метод использует какие значения по умолчанию, легко перепутать перегрузки.
Назначение кода, зависящего от значений по умолчанию для вызовов метода, не ясно. В следующем примере, зависящем от значений по умолчанию, трудно понять, что именно планировал разработчик – порядковое или лингвистическое сравнение двух строк, и может ли разный регистр protocol и "http" привести к тому, что проверка на равенство возвратит значение false.
Dim protocol As String = GetProtocol(url) If String.Equals(protocol, "http") Then ' ...Code to handle HTTP protocol. Else Throw New InvalidOperationException() End If
string protocol = GetProtocol(url); if (String.Equals(protocol, "http")) { // ...Code to handle HTTP protocol. } else { throw new InvalidOperationException(); }
В целом рекомендуется вызывать метод, не зависящий от значений по умолчанию, поскольку они приводят к неоднозначности кода. В свою очередь использование методов, не зависящих от значений по умолчанию, делает код более читаемым и упрощает его отладку и обслуживание. Следующий пример отличается от предыдущего только тем, что в нем очевидно используется порядковое сравнение, и различия в регистрах символов игнорируются.
Dim protocol As String = GetProtocol(url)
If String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase) Then
' ...Code to handle HTTP protocol.
Else
Throw New InvalidOperationException()
End If
string protocol = GetProtocol(url);
if (String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) {
// ...Code to handle HTTP protocol.
}
else {
throw new InvalidOperationException();
}
К началу
Подробные сведения по сравнению строк
Сравнение строк является основой большинства операций со строками, в частности сортировки и проверки на равенство. Строки сортируются в определенном порядке: если в упорядоченном списке строк "my" появляется до "string", то "my" должна сравниваться не больше "string". Кроме того, сравнение неявно задает равенство. Операция сравнения возвращает 0 для строк, которые она сочла равными. Правильная интерпретация этого – ни одна из строк не меньше другой. Наиболее значимые операции со строками включают одну или обе процедуры: сравнение с другой строкой и выполнение правильно определенной операции сортировки.
Однако оценка равенства двух строк или порядка сортировки не гарантирует единственный правильный результат; результат зависит от условия, которое использовалось для сравнения строк. В частности, порядковые сравнения строк и сравнения на основе правил использования прописных и строчных букв и сортировки текущих языка и региональных параметров или инвариантных (независимых от языковых стандартов языка и региональных параметров на основе английского языка) могут приводить к разным результатам.
Сравнения строк, использующие текущие язык и региональные параметры
Один критерий включает использование при сравнении строк правил текущего языка и региональных параметров. Сравнения на основе текущего языка и региональных параметров используют текущий язык и региональные параметры или языковый стандарт потока. Если язык и региональные параметры не заданы пользователем, то по умолчанию принимается значение, установленное в окне Региональные параметры панели управления. Следует всегда использовать сравнения на основе текущего языка и региональных параметров, если сравниваются лингвистические данные, которые отражают взаимодействие с пользователем, учитывающее язык и региональные параметры.
Однако при изменении языка и региональных параметров поведение сравнения и правил использования прописных и строчных букв в .NET Framework изменяется. Это происходит, когда приложение выполняется на компьютере с языком и региональными параметрами, отличными от тех, которые имелись на компьютере разработки приложения, или когда поток выполнения изменяет язык и региональные параметры. Это преднамеренное поведение, но неочевидное для многих разработчиков. В следующем примере показаны различия между американским английским ("en-US") языком и региональными параметрами и шведским ("sv-SE") языком и региональными параметрами. Обратите внимание, что слова "ångström", "Windows" и "Visual Studio" появляются в упорядоченных массивах строк в разных позициях.
Imports System.Globalization
Imports System.Threading
Module Example
Public Sub Main()
Dim values() As String = { "able", "ångström", "apple", _
"Æble", "Windows", "Visual Studio" }
Array.Sort(values)
DisplayArray(values)
' Change culture to Swedish (Sweden).
Dim originalCulture As String = CultureInfo.CurrentCulture.Name
Thread.CurrentThread.CurrentCulture = New CultureInfo("sv-SE")
Array.Sort(values)
DisplayArray(values)
' Restore the original culture.
Thread.CurrentThread.CurrentCulture = New CultureInfo(originalCulture)
End Sub
Private Sub DisplayArray(values() As String)
Console.WRiteLine("Sorting using the {0} culture:", _
CultureInfo.CurrentCulture.Name)
For Each value As String In values
Console.WriteLine(" {0}", value)
Next
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' Sorting using the en-US culture:
' able
' Æble
' ångström
' apple
' Visual Studio
' Windows
'
' Sorting using the sv-SE culture:
' able
' Æble
' apple
' Windows
' Visual Studio
' ångström
using System;
using System.Globalization;
using System.Threading;
public class Example
{
public static void Main()
{
string[] values= { "able", "ångström", "apple", "Æble",
"Windows", "Visual Studio" };
Array.Sort(values);
DisplayArray(values);
// Change culture to Swedish (Sweden).
string originalCulture = CultureInfo.CurrentCulture.Name;
Thread.CurrentThread.CurrentCulture = new CultureInfo("sv-SE");
Array.Sort(values);
DisplayArray(values);
// Restore the original culture.
Thread.CurrentThread.CurrentCulture = new CultureInfo(originalCulture);
}
private static void DisplayArray(string[] values)
{
Console.WriteLine("Sorting using the {0} culture:",
CultureInfo.CurrentCulture.Name);
foreach (string value in values)
Console.WriteLine(" {0}", value);
Console.WriteLine();
}
}
// The example displays the following output:
// Sorting using the en-US culture:
// able
// Æble
// ångström
// apple
// Visual Studio
// Windows
//
// Sorting using the sv-SE culture:
// able
// Æble
// apple
// Windows
// Visual Studio
// ångström
Учитывающие регистр сравнения, использующие текущий язык и региональные параметры, совпадают со сравнениями, учитывающими язык и региональные параметры, за исключением того, что в первых сравнениях не учитывается регистр, как того требуют текущие язык и региональные параметры потока. Это поведение может также самостоятельно проявляться в порядках сортировки.
Сравнения, использующие семантику текущего языка и региональных параметров, являются сравнениями по умолчанию для следующих методов:
перегрузок String.Compare, не включающих параметр StringComparison;
перегрузок String.CompareTo;
метода по умолчанию String.StartsWith(String) и метода String.StartsWith(String, Boolean, CultureInfo) с параметром null CultureInfo;
метода по умолчанию String.EndsWith(String) и метода String.EndsWith(String, Boolean, CultureInfo) с параметром null CultureInfo;
перегрузок String.IndexOf, принимающих String в качестве параметра поиска и не имеющих параметра StringComparison;
перегрузок String.LastIndexOf, принимающих String в качестве параметра поиска и не имеющих параметра StringComparison.
В любом случае рекомендуется вызвать перегрузку, имеющую параметр StringComparison, чтобы сделать ясным смысл вызова метода.
При лингвистической интерпретации нелингвистических строковых данных или при интерпретации строковых данных определенного языка и региональных параметров с использованием правил другого языка и региональных параметров могут возникать трудно и не очень трудно выявляемые ошибки. Типичный пример – проблема с турецкой буквой I.
Почти во всех латинских алфавитах, включая американский английский, символ "i" (\u0069) является строчной версией символа "I" (\u0049). Это правило использования прописных и строчных букв стало само собой разумеющимся для тех, кто программирует для таких языков. Однако в турецком ("tr-TR") алфавите имеется буква "I с точкой", "İ" (\u0130), которая является прописной версией буквы "i". В турецком языке также имеется символ "i без точки", буква "ı" (\u0131), которая является строчной версией прописной буквы "I". Эта же особенность имеется и в азербайджанском языке ("az").
Следовательно, предположения о прописной "i" или строчной "I" не являются правильными для всех языков. При использовании перегрузок по умолчанию для процедур сравнения строк они будут меняться в зависимости от языка и региональных параметров. Если сравниваемые данные не являются лингвистическими, использование перегрузок по умолчанию может привести к нежелательным результатам, как показывает следующая попытка выполнения сравнения строк "file" и "FILE" без учета регистра.
Imports System.Globalization
Imports System.Threading
Module Example
Public Sub Main()
Dim fileUrl = "file"
Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
Console.WriteLine("Culture = {0}", _
Thread.CurrentThread.CurrentCulture.DisplayName)
Console.WriteLine("(file == FILE) = {0}", _
fileUrl.StartsWith("FILE", True, Nothing))
Console.WriteLine()
Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")
Console.WriteLine("Culture = {0}", _
Thread.CurrentThread.CurrentCulture.DisplayName)
Console.WriteLine("(file == FILE) = {0}", _
fileUrl.StartsWith("FILE", True, Nothing))
End Sub
End Module
' The example displays the following output:
' Culture = English (United States)
' (file == FILE) = True
'
' Culture = Turkish (Turkey)
' (file == FILE) = False
using System;
using System.Globalization;
using System.Threading;
public class Example
{
public static void Main()
{
string fileUrl = "file";
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Console.WriteLine("Culture = {0}",
Thread.CurrentThread.CurrentCulture.DisplayName);
Console.WriteLine("(file == FILE) = {0}",
fileUrl.StartsWith("FILE", true, null));
Console.WriteLine();
Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
Console.WriteLine("Culture = {0}",
Thread.CurrentThread.CurrentCulture.DisplayName);
Console.WriteLine("(file == FILE) = {0}",
fileUrl.StartsWith("FILE", true, null));
}
}
// The example displays the following output:
// Culture = English (United States)
// (file == FILE) = True
//
// Culture = Turkish (Turkey)
// (file == FILE) = False
Это сравнение может вызвать значительные проблемы, если язык и региональные параметры случайно использовались в важных для обеспечения безопасности параметрах, как показано в следующем примере. Вызов метода, например IsFileURI("file:"), возвращает значение true, если текущий язык является американским английским, и значение false, если текущий язык – турецкий. Следовательно, в турецких системах кто-нибудь может обойти меры безопасности, блокирующие доступ к независящим от регистра универсальным кодам ресурсов (URI), начинающимся с "FILE:".
Public Shared Function IsFileURI(path As String) As Boolean
Return path.StartsWith("FILE:", True, Nothing)
End Function
public static bool IsFileURI(String path)
{
return path.StartsWith("FILE:", true, null);
}
В этом случае, поскольку "file:" положено интерпретировать как не лингвистический и не зависящий от языка и региональных параметров идентификатор, должен быть написан другой код, показанный в следующем примере.
Public Shared Function IsFileURI(path As String) As Boolean
Return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase)
End Function
public static bool IsFileURI(string path)
{
path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
return true;
}
Однако в предыдущем примере для проверки на равенство используется метод String.StartsWith(String, StringComparison). Поскольку цель сравнения состоит в проверке на равенство, а не в упорядочивании строк, наилучшей альтернативой является вызов метода Equals, как показано в следующем примере.
Public Shared Function IsFileURI(path As String) As Boolean
If (path.Length < 5) Then Return False
Return String.Equals(path.Substring(0, 5), "FILE:", _
StringComparison.OrdinalIgnoreCase)
End Function
public static bool IsFileURI(string path)
{
if (path.Length < 5) return false;
return String.Equals(path.Substring(0, 5), "FILE:",
StringComparison.OrdinalIgnoreCase);
}
Операции по порядковому сравнению строк
Указание значения StringComparison.Ordinal или StringComparison.OrdinalIgnoreCase в вызове метода означает нелингвистическое сравнение, при котором особенности национальных языков не учитываются. Методы, которые вызываются с этими значениями StringComparison, строят операции сравнения на простых побайтовых сравнениях вместо правил использования прописных и строчных букв или таблиц эквивалентности, параметризованных по языку и региональным параметрам. В большинстве случаев такой подход наилучшим образом соответствует предполагаемой интерпретации строк, делая код более быстрым и более читаемым.
Порядковые сравнения – это сравнения строк, при которых все байты каждой строки сравниваются без лингвистической интерпретации; например "windows" не соответствует "Windows". В сущности, это вызов функции strcmp среды выполнения C. Следует использовать такое сравнение, когда контекст указывает, что строки должны точно совпадать, или требует консервативную политику соответствия. Кроме того, порядковое сравнение является самой быстрой операцией сравнения, поскольку в нем не применяются лингвистические правила для определения результата.
Строки в .NET Framework могут содержать включенные символы NULL. Одно из самых очевидных различий между порядковым сравнением и сравнением с учетом языка и региональных параметров (включая сравнения, использующие инвариантный язык и региональные параметры) заключается в обработке включенных в строку символов NULL. При использовании методов String.Compare и String.Equals для выполнения сравнений с учетом языка и региональных параметров (включая сравнения, использующие инвариантный язык и региональные параметры) эти символы игнорируются. В результате при сравнениях, учитывающих язык и региональные параметры, строки, содержащие символы NULL, могут считаться равными строкам без этих символов.
Важно |
---|
Хотя методы сравнения строк пропускают включенные символы NULL, методы поиска строк, такие как String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOf и String.StartsWith, их не пропускают. |
В следующем примере выполняется сравнение с учетом языка и региональных параметров строки "Aa" с аналогичной строкой, содержащей несколько символов NULL между "A" и "a", и показывается, каким образом эти строки рассматриваются как равные.
Module Example
Public Sub Main()
Dim str1 As String = "Aa"
Dim str2 As String = "A" + New String(Convert.ToChar(0), 3) + "a"
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", _
str1, ShowBytes(str1), str2, ShowBytes(str2))
Console.WriteLine(" With String.Compare:")
Console.WriteLine(" Current Culture: {0}", _
String.Compare(str1, str2, StringComparison.CurrentCulture))
Console.WriteLine(" Invariant Culture: {0}", _
String.Compare(str1, str2, StringComparison.InvariantCulture))
Console.WriteLine(" With String.Equals:")
Console.WriteLine(" Current Culture: {0}", _
String.Equals(str1, str2, StringComparison.CurrentCulture))
Console.WriteLine(" Invariant Culture: {0}", _
String.Equals(str1, str2, StringComparison.InvariantCulture))
End Sub
Private Function ShowBytes(str As String) As String
Dim hexString As String = String.Empty
For ctr As Integer = 0 To str.Length - 1
Dim result As String = String.Empty
result = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
result = " " + result.Substring(0,2) + " " + result.Substring(2, 2)
hexString += result
Next
Return hexString.Trim()
End Function
End Module
using System;
public class Example
{
public static void Main()
{
string str1 = "Aa";
string str2 = "A" + new String('\u0000', 3) + "a";
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):",
str1, ShowBytes(str1), str2, ShowBytes(str2));
Console.WriteLine(" With String.Compare:");
Console.WriteLine(" Current Culture: {0}",
String.Compare(str1, str2, StringComparison.CurrentCulture));
Console.WriteLine(" Invariant Culture: {0}",
String.Compare(str1, str2, StringComparison.InvariantCulture));
Console.WriteLine(" With String.Equals:");
Console.WriteLine(" Current Culture: {0}",
String.Equals(str1, str2, StringComparison.CurrentCulture));
Console.WriteLine(" Invariant Culture: {0}",
String.Equals(str1, str2, StringComparison.InvariantCulture));
}
private static string ShowBytes(string str)
{
string hexString = String.Empty;
for (int ctr = 0; ctr < str.Length; ctr++)
{
string result = String.Empty;
result = Convert.ToInt32(str[ctr]).ToString("X4");
result = " " + result.Substring(0,2) + " " + result.Substring(2, 2);
hexString += result;
}
return hexString.Trim();
}
}
// The example displays the following output:
// Comparing 'Aa' (00 41 00 61) and 'A a' (00 41 00 00 00 00 00 00 00 61):
// With String.Compare:
// Current Culture: 0
// Invariant Culture: 0
// With String.Equals:
// Current Culture: True
// Invariant Culture: True
Однако при использовании порядкового сравнения эти строки не получатся равными, как показано в следующем примере.
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", _
str1, ShowBytes(str1), str2, ShowBytes(str2))
Console.WriteLine(" With String.Compare:")
Console.WriteLine(" Ordinal: {0}", _
String.Compare(str1, str2, StringComparison.Ordinal))
Console.WriteLine(" With String.Equals:")
Console.WriteLine(" Ordinal: {0}", _
String.Equals(str1, str2, StringComparison.Ordinal))
' The example displays the following output:
' Comparing 'Aa' (00 41 00 61) and 'A a' (00 41 00 00 00 00 00 00 00 61):
' With String.Compare:
' Ordinal: 97
' With String.Equals:
' Ordinal: False
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):",
str1, ShowBytes(str1), str2, ShowBytes(str2));
Console.WriteLine(" With String.Compare:");
Console.WriteLine(" Ordinal: {0}",
String.Compare(str1, str2, StringComparison.Ordinal));
Console.WriteLine(" With String.Equals:");
Console.WriteLine(" Ordinal: {0}",
String.Equals(str1, str2, StringComparison.Ordinal));
// The example displays the following output:
// Comparing 'Aa' (00 41 00 61) and 'A a' (00 41 00 00 00 00 00 00 00 61):
// With String.Compare:
// Ordinal: 97
// With String.Equals:
// Ordinal: False
Порядковые сравнения без учета регистра являются следующим из наиболее консервативных подходов. В таких сравнениях игнорируется большая часть правил использования прописных и строчных букв; например "windows" соответствует "Windows". При работе с символами ASCII эта политика эквивалентна StringComparison.Ordinal, за исключением того, что игнорирует обычные правила использования прописных и строчных букв ASCII. Следовательно, любой символ в диапазоне [A, Z] (\u0041-\u005A) равен соответствующему символу в диапазоне [a, z] (\u0061-\007A). Правила использования прописных и строчных букв вне диапазона ASCII используют таблицы инвариантного языка и региональных параметров. Таким образом, сравнение
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);
эквивалентно следующему сравнению (но гораздо быстрее):
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(),
StringComparison.Ordinal)
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(),
StringComparison.Ordinal);
Оба эти сравнения довольно быстрые.
Примечание |
---|
Поведение строк файловой системы, разделов и значений реестра и переменных среды наилучшим образом представляется сравнением StringComparison.OrdinalIgnoreCase. |
Оба сравнения, StringComparison.Ordinal и StringComparison.OrdinalIgnoreCase, используют напрямую двоичные значения и наиболее подходят для сопоставления. Если нет уверенности насчет параметров сравнения, следует использовать одно из этих двух значений. Однако поскольку они используют побайтовое сравнение, упорядочивание выполняется не в соответствии с лингвистическим порядком сортировки (как в английском словаре), а в соответствии с двоичным порядком сортировки. В большинстве контекстов результат может отображаться для пользователя с искажениями.
Порядковая семантика используется по умолчанию в перегрузках String.Equals, в которых отсутствует аргумент StringComparison (включая оператор равенства). В любом случае рекомендуется вызывать перегрузку, имеющую параметр StringComparison.
Операции со строками, использующие инвариантный язык и региональные параметры
Сравнения с учетом инвариантного языка и региональных параметров используют свойство CompareInfo, возвращаемое статическим свойством CultureInfo.InvariantCulture. Это поведение одинаково во всех системах; оно преобразовывает любые символы вне собственного диапазона в те, которые с его точки зрения эквивалентны инвариантным символам. Эта политика может пригодиться для обслуживания одного набора поведения строк в нескольких средах языков и региональных параметров, но часто приводит к неожиданным результатам.
Сравнения без учета регистра и с учетом инвариантного языка и региональных параметров также используют для получения сведений о сравнении статическое свойство CompareInfo, возвращаемое статическим свойством CultureInfo.InvariantCulture. Любые различия регистров в этих преобразуемых символах игнорируются.
Сравнения. использующие StringComparison.InvariantCulture и StringComparison.Ordinal, работают со строками ASCII одинаково. Однако StringComparison.InvariantCulture принимает лингвистические решения, которые могут не подходить для строк, которые интерпретируются как набор байтов. Объект CultureInfo.InvariantCulture.CompareInfo создает метод Compare, который интерпретирует разные наборы символов как равные. Например, для инвариантного языка и региональных параметров верно следующее равенство:
InvariantCulture: a + ̊ = å
Символ "a" (\u0061), обозначающий СТРОЧНУЮ ЛАТИНСКУЮ БУКВУ A, находящийся рядом с символом ПРИСОЕДИНЕНИЯ ВЕРХНЕГО КРУЖКА "+ ̊" (\u030a), интерпретируется как символ СТРОЧНОЙ ЛАТИНСКОЙ БУКВЫ A С КРУЖКОМ ВВЕРХУ "å" (\u00e5). Как показано в следующем примере, это поведение отличается от поведения порядкового сравнения.
Dim separated As String = ChrW(&h61) + ChrW(&h30a)
Dim combined As String = ChrW(&he5)
Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}", _
separated, combined, _
String.Compare(separated, combined, _
StringComparison.InvariantCulture) = 0)
Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}", _
separated, combined, _
String.Compare(separated, combined, _
StringComparison.Ordinal) = 0)
' The example displays the following output:
' Equal sort weight of a° and å using InvariantCulture: True
' Equal sort weight of a° and å using Ordinal: False
string separated = "\u0061\u030a";
string combined = "\u00e5";
Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}",
separated, combined,
String.Compare(separated, combined,
StringComparison.InvariantCulture) == 0);
Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}",
separated, combined,
String.Compare(separated, combined,
StringComparison.Ordinal) == 0);
// The example displays the following output:
// Equal sort weight of a° and å using InvariantCulture: True
// Equal sort weight of a° and å using Ordinal: False
При интерпретации имен файлов, файлов Cookie или других объектов, в которых может встречаться такая комбинация, как "å", порядковые сравнения все еще предлагают наиболее прозрачное и подходящее поведение.
В итоге инвариантный язык и региональные параметры имеют очень мало свойств, которые делают их подходящими для использования при сравнении. Сравнение выполняется лингвистическим способом, что препятствует гарантии полного равенства символов, но это не является выбором для отображения в среде какого-либо языка и региональных параметров. Одной из немногих причин использования StringComparison.InvariantCulture для сравнения является наличие упорядоченных данных, что обеспечивает одинаковое отображение для всех языков и региональных параметров. Например, если к приложению прилагается большой файл данных, содержащий список упорядоченных идентификаторов для отображения, дополнение этого списка потребует вставку с сортировкой в инвариантном стиле.
К началу
Выбор элемента StringComparison для вызова метода
В следующей таблице приведено сопоставление семантического контекста строк с элементом перечисления StringComparison.
Данные |
Назначение |
Соответствующее System.StringComparison value |
---|---|---|
Внутренние идентификаторы с учетом регистра. Внутренние идентификаторы с учетом регистра в таких стандартах, как XML и HTTP. Параметры, относящиеся к безопасности, с учетом регистра. |
Не лингвистический идентификатор, в котором байты сопоставляются точно. |
|
Внутренние идентификаторы без учета регистра. Внутренние идентификаторы без учета регистра в таких стандартах, как XML и HTTP. Пути файлов. Разделы и значения реестра. Переменные среды. Идентификаторы ресурсов (например имена дескрипторов). Параметры, относящиеся к безопасности, без учета регистра. |
Не лингвистические идентификаторы, в которых регистр не учитывается; в частности данные, хранящиеся в большинстве системных служб Windows. |
|
Некоторые хранящиеся лингвистические данные. Отображение лингвистических данных, требующее фиксированный порядок сортировки. |
Данные, не зависящие от языка и региональных параметров, но являющиеся лингвистическими. |
– или – |
Отображаемые для пользователя данные. Большая часть ввода пользователя. |
Данные, которым необходимы локальные лингвистические настройки. |
– или – |
К началу
Наиболее распространенные методы сравнения строк в .NET Framework
В следующих разделах рассматриваются методы, наиболее часто используемые для сравнения строк.
String.Compare
Интерпретация по умолчанию: StringComparison.CurrentCulture.
Поскольку операция является основной для интерпретации строк, все экземпляры этих вызовов методов необходимо проверить, чтобы определить, должны ли строки интерпретироваться в соответствии с текущим языком и региональными параметрами или отдельно от языка и региональных параметров (по символам). Обычно выбирается последнее, и тогда должно использоваться сравнение StringComparison.Ordinal.
Класс System.Globalization.CompareInfo, возвращаемый свойством CultureInfo.CompareInfo, также включает метод Compare, который предоставляет большое количество вариантов сопоставления (порядковое, без учета пробелов, без учета типа каны и т.п.) с помощью перечисления флагов CompareOptions.
String.CompareTo
Интерпретация по умолчанию: StringComparison.CurrentCulture.
Этот метод в настоящий момент не предлагает перегрузку, задающую тип StringComparison. Обычно возможно преобразовать эти методы в рекомендованную форму String.Compare(String, String, StringComparison).
Типы, реализующие интерфейсы IComparable и IComparable<T>, реализуют этот метод. Поскольку в нем не предусмотрен параметр StringComparison, реализуемые типы часто дают пользователям возможность указать StringComparer в своем конструкторе. В следующем примере определяется класс FileName, чей конструктор включает параметр StringComparer. Затем этот объект StringComparer используется в методе FileName.CompareTo.
Public Class FileName : Implements IComparable
Dim fname As String
Dim comparer As StringComparer
Public Sub New(name As String, comparer As StringComparer)
If String.IsNullOrEmpty(name) Then
Throw New ArgumentNullException("name")
End If
Me.fname = name
If comparer IsNot Nothing Then
Me.comparer = comparer
Else
Me.comparer = StringComparer.OrdinalIgnoreCase
End If
End Sub
Public ReadOnly Property Name As String
Get
Return fname
End Get
End Property
Public Function CompareTo(obj As Object) As Integer _
Implements IComparable.CompareTo
If obj Is Nothing Then Return 1
If Not TypeOf obj Is FileName Then
obj = obj.ToString()
Else
obj = CType(obj, FileName).Name
End If
Return comparer.Compare(Me.fname, obj)
End Function
End Class
using System;
public class FileName : IComparable
{
string fname;
StringComparer comparer;
public FileName(string name, StringComparer comparer)
{
if (String.IsNullOrEmpty(name))
throw new ArgumentNullException("name");
this.fname = name;
if (comparer != null)
this.comparer = comparer;
else
this.comparer = StringComparer.OrdinalIgnoreCase;
}
public string Name
{
get { return fname; }
}
public int CompareTo(object obj)
{
if (obj == null) return 1;
if (! (obj is FileName))
return comparer.Compare(this.fname, obj.ToString());
else
return comparer.Compare(this.fname, ((FileName) obj).Name);
}
}
String.Equals
Интерпретация по умолчанию: StringComparison.Ordinal.
Класс String предоставляет возможность проверки на равенство путем вызова перегруженного статического метода или перегруженного метода экземпляра Equals. По умолчанию перегрузки и оператор используют порядковое сравнение. Однако рекомендуется вызывать перегрузку, явно задающую тип StringComparison, даже если планируется выполнять порядковое сравнение; это облегчит поиск кода для разных интерпретаций строк.
String.ToUpper и String.ToLower
Интерпретация по умолчанию: StringComparison.CurrentCulture.
Следует с осторожностью использовать эти методы, поскольку преобразование строки в верхний или нижний регистр часто используется в качестве небольшой нормализации для сравнения строк независимо от их регистра. В таком случае следует подумать об использовании сравнения без учета регистра.
Возможно также использование методов String.ToUpperInvariant и String.ToLowerInvariant. Метод ToUpperInvariant – это стандартный способ нормализации регистра. Сравнения, которые делаются с помощью StringComparison.OrdinalIgnoreCase, поведенчески являются объединением двух вызовов: вызова метода ToUpperInvariant для обоих строковых аргументов и сравнения с помощью метода StringComparison.Ordinal.
Перегрузки также возможно использовать для преобразования в верхний и нижний регистр в среде конкретного языка и региональных параметров путем передачи в метод объекта CultureInfo, представляющего этот язык и региональные параметры.
Char.ToUpper и Char.ToLower
Интерпретация по умолчанию: StringComparison.CurrentCulture.
Эти методы работают аналогично методам String.ToUpper и String.ToLower, которые рассматривались в предыдущем разделе.
String.StartsWith и String.EndsWith
Интерпретация по умолчанию: StringComparison.CurrentCulture.
По умолчанию оба этих метода выполняют сравнение с учетом языка и региональных параметров.
String.IndexOf и String.LastIndexOf
Интерпретация по умолчанию: StringComparison.CurrentCulture.
Имеется некоторая непоследовательность в том, как перегрузки этих методов по умолчанию выполняют сравнения. Все методы String.IndexOf и String.LastIndexOf, включающие параметр Char, выполняют порядковое сравнение, но методы по умолчанию String.IndexOf и String.LastIndexOf, включающие параметр String, выполняют сравнение с учетом языка и региональных параметров.
При вызове метода String.IndexOf(String) или String.LastIndexOf(String) и передаче его в строку для обнаружения в текущем экземпляре рекомендуется вызывать перегрузку, которая явно указывает тип StringComparison. Перегрузки, включающие аргумент Char, не разрешают указывать тип StringComparison.
К началу
Методы неявного сравнения строк
Некоторые не строковые методы, основной операцией которых является сравнение строк, используют тип StringComparer. Класс StringComparer включает шесть статических свойств, возвращающих экземпляры StringComparer, методы StringComparer.Compare которых выполняют следующие типы сравнений строк:
Сравнения строк с учетом языка и региональных параметров, использующие текущий язык и региональные параметры. Этот объект StringComparer возвращается свойством StringComparer.CurrentCulture.
Сравнения без учета регистра и с использованием текущего языка и региональных параметров. Этот объект StringComparer возвращается свойством StringComparer.CurrentCultureIgnoreCase.
Сравнения без учета языка и региональных параметров, использующие правила сравнения слов инвариантного языка и региональных параметров. Этот объект StringComparer возвращается свойством StringComparer.InvariantCulture.
Сравнения без учета регистра, языка и региональных параметров, использующие правила сравнения слов инвариантного языка и региональных параметров. Этот объект StringComparer возвращается свойством StringComparer.InvariantCultureIgnoreCase.
Порядковое сравнение. Этот объект StringComparer возвращается свойством StringComparer.Ordinal.
Порядковое сравнение без учета регистра. Этот объект StringComparer возвращается свойством StringComparer.OrdinalIgnoreCase.
Array.Sort и Array.BinarySearch
Интерпретация по умолчанию: StringComparison.CurrentCulture.
При сохранении каких-либо данных в коллекции или считывании существующих данных из файла или базы данных в коллекцию переключение текущего языка и региональных параметров может сделать инварианты в коллекции недействительными. Метод Array.BinarySearch предполагает, что элементы массива, которые следует искать, уже отсортированы. Для сортировки каких-либо строковых элементов в массиве метод Array.Sort вызывает метод String.Compare для упорядочивания отдельных элементов. Использование механизма сравнения, учитывающего язык и региональные параметры, может быть опасным, если в промежутке между сортировкой массива и поиском по его содержимому язык и региональные параметры изменятся. Например, в следующем коде операции хранения и извлечения работают с механизмом сравнения, который неявно предоставляется свойством Thread.CurrentThread.CurrentCulture. Если может произойти изменение языка и региональных параметров между вызовами StoreNames и DoesNameExist, и особенно если содержимое массива постоянно присутствует где-нибудь еще между этими двумя вызовами методов, двоичный поиск может завершиться неудачно.
' Incorrect.
Dim storedNames() As String
Public Sub StoreNames(names() As String)
Dim index As Integer = 0
ReDim storedNames(names.Length - 1)
For Each name As String In names
Me.storedNames(index) = name
index+= 1
Next
Array.Sort(names) ' Line A.
End Sub
Public Function DoesNameExist(name As String) As Boolean
Return Array.BinarySearch(Me.storedNames, name) >= 0 ' Line B.
End Function
// Incorrect.
string []storedNames;
public void StoreNames(string [] names)
{
int index = 0;
storedNames = new string[names.Length];
foreach (string name in names)
{
this.storedNames[index++] = name;
}
Array.Sort(names); // Line A.
}
public bool DoesNameExist(string name)
{
return (Array.BinarySearch(this.storedNames, name) >= 0); // Line B.
}
Рекомендуемый вариант показан в следующем примере, в котором для сортировки массива и для поиска в нем используется одно и то же порядковое (не учитывающее язык и региональные параметры) сравнение. Изменения кода показаны в строках с метками Line A и Line B в этих двух примерах.
' Correct.
Dim storedNames() As String
Public Sub StoreNames(names() As String)
Dim index As Integer = 0
ReDim storedNames(names.Length - 1)
For Each name As String In names
Me.storedNames(index) = name
index+= 1
Next
Array.Sort(names, StringComparer.Ordinal) ' Line A.
End Sub
Public Function DoesNameExist(name As String) As Boolean
Return Array.BinarySearch(Me.storedNames, name, StringComparer.Ordinal) >= 0 ' Line B.
End Function
// Correct.
string []storedNames;
public void StoreNames(string [] names)
{
int index = 0;
storedNames = new string[names.Length];
foreach (string name in names)
{
this.storedNames[index++] = name;
}
Array.Sort(names, StringComparer.Ordinal); // Line A.
}
public bool DoesNameExist(string name)
{
return (Array.BinarySearch(this.storedNames, name, StringComparer.Ordinal) >= 0); // Line B.
}
Если эти данные существуют и перемещаются в разных средах языка и региональных параметров, и для представления данных пользователю применяется сортировка, следует подумать об использовании сравнения StringComparison.InvariantCulture, которое работает лингвистически для улучшения представления данных пользователю, но не подвержено влиянию смены языка и региональных параметров. В следующем примере показано, как изменить два предыдущих примера и использовать для сортировки массива и поиска по нему инвариантный язык и региональные параметры.
' Correct.
Dim storedNames() As String
Public Sub StoreNames(names() As String)
Dim index As Integer = 0
ReDim storedNames(names.Length - 1)
For Each name As String In names
Me.storedNames(index) = name
index+= 1
Next
Array.Sort(names, StringComparer.InvariantCulture) ' Line A.
End Sub
Public Function DoesNameExist(name As String) As Boolean
Return Array.BinarySearch(Me.storedNames, name, StringComparer.InvariantCulture) >= 0 ' Line B.
End Function
// Correct.
string []storedNames;
public void StoreNames(string [] names)
{
int index = 0;
storedNames = new string[names.Length];
foreach (string name in names)
{
this.storedNames[index++] = name;
}
Array.Sort(names, StringComparer.InvariantCulture); // Line A.
}
public bool DoesNameExist(string name)
{
return (Array.BinarySearch(this.storedNames, name, StringComparer.InvariantCulture) >= 0); // Line B.
}
Пример коллекций. Конструктор таблицы хэширования
Хэшированные строки обеспечивают второй пример операции, на которую влияет способ сравнения строк.
В следующем примере объект Hashtable создается путем передачи его в объект StringComparer, возвращаемый свойством StringComparer.OrdinalIgnoreCase. Поскольку класс StringComparer, производный от StringComparer, реализует интерфейс IEqualityComparer, его метод GetHashCode используется для вычисления хэш-кода строк в хэш-таблице.
Const initialTableCapacity As Integer = 100
Dim h As Hashtable
Public Sub PopulateFileTable(dir As String)
h = New Hashtable(initialTableCapacity, _
StringComparer.OrdinalIgnoreCase)
For Each filename As String In Directory.GetFiles(dir)
h.Add(filename, File.GetCreationTime(filename))
Next
End Sub
Public Sub PrintCreationTime(targetFile As String)
Dim dt As Object = h(targetFile)
If dt IsNot Nothing Then
Console.WriteLine("File {0} was created at {1}.", _
targetFile, _
CDate(dt))
Else
Console.WriteLine("File {0} does not exist.", targetFile)
End If
End Sub
const int initialTableCapacity = 100;
Hashtable h;
public void PopulateFileTable(string directory)
{
h = new Hashtable(initialTableCapacity,
StringComparer.OrdinalIgnoreCase);
foreach (string file in Directory.GetFiles(directory))
h.Add(file, File.GetCreationTime(file));
}
public void PrintCreationTime(string targetFile)
{
Object dt = h[targetFile];
if (dt != null)
{
Console.WriteLine("File {0} was created at time {1}.",
targetFile,
(DateTime) dt);
}
else
{
Console.WriteLine("File {0} does not exist.", targetFile);
}
}
К началу