Поиск…
замечания
В .NET-строках System.String
- это последовательность символов System.Char
, каждый символ - кодированный код UTF-16. Это различие важно , потому что говорят определение языка характера и .NET (и многих других языков) определение характера различны.
Один символ , который должен быть правильно назван grapheme , отображается в виде глифа и определяется одним или несколькими кодовыми точками Unicode. Каждая кодовая точка затем кодируется в последовательности блоков кода . Теперь должно быть понятно, почему один System.Char
не всегда представляет собой графем, давайте посмотрим в реальном мире, как они отличаются:
- Одна графема из-за сочетания символов может приводить к двум или более кодовым точкам: а состоит из двух кодовых точек: U + 0061 LATIN SMALL LETTER A и U + 0300 COMBINING GRAVE ACCENT . Это самая распространенная ошибка, потому что
"à".Length == 2
то время как вы можете ожидать1
. - Существуют дублированные символы, например, à может быть одной кодовой точкой U + 00E0 ЛАТИНСКОЕ МАЛОЕ ПИСЬМО A С GRAVE или двумя кодовыми точками, как описано выше. Очевидно, что они должны сравнивать одно и то же:
"\u00e0" == "\u0061\u0300"
(даже если"\u00e0".Length != "\u0061\u0300".Length
). Это возможно из-за нормализации строки, выполняемой методомString.Normalize()
. - Последовательность Unicode может содержать сгруппированную или разложенную последовательность, например символ 한 U + D55C HAN CHARACTER может быть одной кодовой точкой (закодированной как единый блок кода в UTF-16) или разложенной последовательностью его слогов ᄒ , ᅡ и ᆫ . Их нужно сравнивать равными.
- Одна кодовая точка может быть закодирована в несколько кодовых единиц: символ 𠂊 U + 2008A HAN CHARACTER кодируется как два
System.Char
("\ud840\udc8a"
), даже если это всего лишь одна кодовая точка: UTF-16 кодировка не фиксированный размер! Это источник бесчисленных ошибок (также серьезных ошибок безопасности), если, например, ваше приложение применяет максимальную длину и слепо обрезает строку, тогда вы можете создать недопустимую строку. - Некоторые языки имеют орграф и триграфы, например , в Чехии ч является автономным письмом (после часов и , прежде чем я тогда при заказе списка строк вы будете иметь fyzika перед тем Chemie.
Есть гораздо больше проблем с обработкой текста, см., Например, Как я могу использовать символ Unicode для сравнения символов? для более широкого введения и дополнительных ссылок на соответствующие аргументы.
В общем случае при работе с международным текстом вы можете использовать эту простую функцию для перечисления текстовых элементов в строке (избегая прерывания суррогатов и кодирования Unicode):
public static class StringExtensions
{
public static IEnumerable<string> EnumerateCharacters(this string s)
{
if (s == null)
return Enumerable.Empty<string>();
var enumerator = StringInfo.GetTextElementEnumerator(s.Normalize());
while (enumerator.MoveNext())
yield return (string)enumerator.Value;
}
}
Подсчет отдельных символов
Если вам нужно подсчитать разные символы, то по причинам, описанным в разделе « Примечания », вы не можете просто использовать свойство Length
потому что это длина массива System.Char
которые не являются символами, а единицами кода (а не кодами Unicode ни графемы). Если, например, вы просто пишете text.Distinct().Count()
вы получите неверные результаты, исправьте код:
int distinctCharactersCount = text.EnumerateCharacters().Count();
Еще один шаг - подсчет вхождений каждого символа , если производительность не является проблемой, вы можете просто сделать это так (в этом примере, независимо от случая):
var frequencies = text.EnumerateCharacters()
.GroupBy(x => x, StringComparer.CurrentCultureIgnoreCase)
.Select(x => new { Character = x.Key, Count = x.Count() };
Количество символов
Если вам нужно подсчитать символы, то по причинам, описанным в разделе « Замечания », вы не можете просто использовать свойство «Длина», потому что это длина массива System.Char
которые не являются символами, а единицами кода (а не кодами Unicode и графемы). Правильный код:
int length = text.EnumerateCharacters().Count();
Небольшая оптимизация может переписать метод расширения EnumerateCharacters()
специально для этой цели:
public static class StringExtensions
{
public static int CountCharacters(this string text)
{
if (String.IsNullOrEmpty(text))
return 0;
int count = 0;
var enumerator = StringInfo.GetTextElementEnumerator(text);
while (enumerator.MoveNext())
++count;
return count;
}
}
Количество экземпляров символа
Из-за причин, объясненных в разделе « Замечания », вы не можете просто сделать это (если вы не хотите подсчитывать вхождения определенного блока кода):
int count = text.Count(x => x == ch);
Вам нужна более сложная функция:
public static int CountOccurrencesOf(this string text, string character)
{
return text.EnumerateCharacters()
.Count(x => String.Equals(x, character, StringComparer.CurrentCulture));
}
Обратите внимание, что сравнение строк (в отличие от сравнения символов, которое является инвариантом культуры) всегда должно выполняться в соответствии с правилами конкретной культуре.
Разделить строку на блоки фиксированной длины
Мы не можем разбить строку на произвольные точки (потому что System.Char
может быть недействительным в одиночку, потому что это комбинационный символ или часть суррогата), тогда код должен учитывать это (обратите внимание, что с длиной я имею в виду количество графем, а не количество кодовых единиц ):
public static IEnumerable<string> Split(this string value, int desiredLength)
{
var characters = StringInfo.GetTextElementEnumerator(value);
while (characters.MoveNext())
yield return String.Concat(Take(characters, desiredLength));
}
private static IEnumerable<string> Take(TextElementEnumerator enumerator, int count)
{
for (int i = 0; i < count; ++i)
{
yield return (string)enumerator.Current;
if (!enumerator.MoveNext())
yield break;
}
}
Преобразование строки в / из другой кодировки
Строки .NET содержат System.Char
(кодовые модули UTF-16). Если вы хотите сохранить (или управлять) текст с другой кодировкой, вам придется работать с массивом System.Byte
.
Преобразования выполняются по классам , полученных из System.Text.Encoder
и System.Text.Decoder
, которые вместе могут конвертировать в / из другого кодирования (от байта X закодированного массива byte[]
к UTF-16 закодированных System.String
и порока -versa).
Поскольку кодировщик / декодер обычно работает очень близко друг к другу, они группируются вместе в классе, производном от System.Text.Encoding
, производные классы предлагают преобразования в / из популярных кодировок (UTF-8, UTF-16 и т. Д.).
Примеры:
Преобразование строки в UTF-8
byte[] data = Encoding.UTF8.GetBytes("This is my text");
Преобразование данных UTF-8 в строку
var text = Encoding.UTF8.GetString(data);
Изменить кодировку существующего текстового файла
Этот код будет считывать содержимое текстового файла с кодировкой UTF-8 и сохранять его обратно в кодировке UTF-16. Обратите внимание, что этот код не является оптимальным, если файл большой, потому что он будет считывать весь его контент в память:
var content = File.ReadAllText(path, Encoding.UTF8);
File.WriteAllText(content, Encoding.UTF16);
Виртуальный метод Object.ToString ()
Все в .NET является объектом, поэтому каждый тип имеет метод ToString()
определенный в классе Object
который может быть переопределен. По умолчанию реализация этого метода возвращает имя типа:
public class Foo
{
}
var foo = new Foo();
Console.WriteLine(foo); // outputs Foo
ToString()
неявно вызывается при объединении значения со строкой:
public class Foo
{
public override string ToString()
{
return "I am Foo";
}
}
var foo = new Foo();
Console.WriteLine("I am bar and "+foo);// outputs I am bar and I am Foo
Результат этого метода также широко используется средствами отладки. Если по какой-то причине вы не хотите переопределять этот метод, но хотите настроить, как отладчик показывает значение вашего типа, используйте атрибут DebuggerDisplay ( MSDN ):
// [DebuggerDisplay("Person = FN {FirstName}, LN {LastName}")]
[DebuggerDisplay("Person = FN {"+nameof(Person.FirstName)+"}, LN {"+nameof(Person.LastName)+"}")]
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set;}
// ...
}
Неизменяемость струн
Строки неизменяемы. Вы просто не можете изменить существующую строку. Любая операция над строкой создает новый экземпляр строки, имеющей новое значение. Это означает, что если вам нужно заменить один символ в очень длинной строке, память будет выделена для нового значения.
string veryLongString = ...
// memory is allocated
string newString = veryLongString.Remove(0,1); // removes first character of the string.
Если вам нужно выполнить множество операций со строковым значением, используйте класс StringBuilder
который предназначен для эффективной обработки строк:
var sb = new StringBuilder(someInitialString);
foreach(var str in manyManyStrings)
{
sb.Append(str);
}
var finalString = sb.ToString();
Сочетание строк
Несмотря на то, что String
является ссылочным типом ==
оператор сравнивает строковые значения, а не ссылки.
Как вы знаете, string
- это всего лишь массив символов. Но если вы считаете, что проверка равенства строк и сравнение производится персонажем по характеру, вы ошибаетесь. Эта операция специфична для культуры (см. Примечания ниже): некоторые последовательности символов можно рассматривать как равные в зависимости от культуры .
Подумайте дважды перед коротким замыканием проверки равенства, сравнив свойства Length
двух строк!
Используйте перегрузки метода String.Equals
которые принимают дополнительное значение перечисления StringComparison
, если вам нужно изменить поведение по умолчанию.