Както споменахме при разглеждането на стандарта Unicode, някои символи се представят с повече от една 16-битова стойност. Тъй като един Char обект съдържа информация за една 16-битова стойност, няма как той да съхрани графеми, съставени от повече от една Unicode стойност.
Ако един низов обект, съдържащ съставни графеми, се обхожда символ по символ, то една графема, която се състои от няколко, примерно три, абстрактни Unicode стойности, няма да бъде коректно разпозната. Тъй като тези стойности са записани в последователни Char обекти, индексаторът на класа String ще разгледа всяка съставна стойност като отделен символ.
Съставните символи могат да бъдат съхранявани в низ, като класът StringInfo дава възможност низовете да се обхождат графема по графема. Чрез метода GetTextElementEnumerator(…) можем да получим итератор по отделните графеми на низа. След това с методите MoveNext() и GetTextElement() можем да осъществяваме достъп до поредната графема.
Използване на StringInfo – пример
Следващият пример илюстрира обхождането на отделните графеми от даден Unicode текст:
String s = "s\u0069\u0307\u0301\u0078\u0323";
TextElementEnumerator graphemes =
StringInfo.GetTextElementEnumerator(s);
StringBuilder text = new StringBuilder();
while (graphemes.MoveNext())
{
string grapheme = graphemes.GetTextElement();
foreach (char ch in grapheme)
{
text.AppendFormat("\\u{0:X4}", (int) ch);
}
text.Append(" = " + grapheme);
text.Append(Environment.NewLine);
}
MessageBox.Show(text.ToString(), s);
|
Резултатът от изпълнението на примера е низ от три символа в заглавието на показаната диалогова кутия и всяка от получените графеми, представена със съответните Unicode стойности, от които се състои.
Друг начин за ползване на класа StringInfo е чрез неговия метод ParseCombiningCharacters(…). Той приема като параметър String обект и връща масив от цели числа, които съдържат началните индекси на отделните единични или съставни символи.
Интерниране на низове
При работа със string обекти често се налага да се извърши сравняване на два низа. Тази операция отнема много системни ресурси и ако нашето приложение предполага много такива операции, то това ще влоши бързодействието на програмата ни.
С цел спестяване на памет CLR поддържа хеш таблица на всички използвани низови константи чрез структурата от данни "intern pool". Всеки символен низ, които се намира в "intern pool", се нарича интерниран.
В тази таблица се записват всички заложени в сорс кода символни низове, като е предвиден и методът System.Intern(String) за добавяне на динамично генерираните символни низове.
При добавянето на нов запис в таблицата първо се проверява дали символният низ не съвпада с вече въведен такъв. Така еднаквите низове се съхраняват в паметта физически на едно място. Сравнението на два интернирани низа става много по-бързо, защото те не се сравняват по съставящите ги символи, а се извършва директно сравнение на референциите им.
Методът System.Intern(…) позволява на програмиста сам да интернира динамично генериран низ по време на изпълнение на програмата, ако той не съществува в таблицата, и връща референция към него.
Методът String.IsInterned(…) проверява дали даден низ съществува в хеш таблицата и връща референция към него, ако вече е добавен. В противен случай връща null.
Всички символни низове, заложени като константи в сорс кода, винаги се добавят в хеш таблицата "intern pool", но това не важи за низовете, дефинирани по време на изпълнение. Нека разгледаме следния пример:
string s1 = "Няма бира!";
string s2 = "Няма ";
s2 = s2 + "бира!";
Console.WriteLine(Object.ReferenceEquals(s1,s2)); // False
s2 = String.Intern(s2);
Console.WriteLine(Object.ReferenceEquals(s1, s2)); // True
|
На първия ред дефинираме String обект, като стойността му ще се запише в хеш таблицата на "intern pool", когато то се компилира от JIT компилатора.
Тъй като s2 е дефиниран динамично, стойността му не се записва в "intern pool" и методът Object.ReferenceEquals(s1, s2) ще върне false.
За да интернираме s2, използваме статичния метод String.Intern(), който приема като параметър низ и търси в хеш таблицата "intern pool". Ако подаденият низ бъде намерен, методът връща референция към вече съществуващото поле.
Форматиращи низове
Възможностите на .NET Framework за форматиране ни позволяват да преобразуваме обект в низ, който е подходящ за извеждане на екрана. С форматиращи низове лесно може да представим информацията, предназначена за потребителя, в четим и разбираем вид.
Следните методи използват форматиращи низове:
-
IFormattable.ToString(string format)
-
String.Format(string format, …)
-
Console.WriteLine(…)
-
StringBuffer.AppendFormat(…)
Използване на форматиращи символи
Форматирането се задава с форматиращи символи и комбинации от тях. Ето няколко примера:
string s = 42.ToString("C5"); // s = "00042"
string s = String.Format("{0:P2}", 0.374); // s = "37,4%"
Console.WriteLine("Приходите за {0:d.MM.yyyy г.} са " +
"{1:C2}", DateTime.Now, 1872.43);
// Result: Приходите за 4.08.2004 г. са 1 872,43 лв
|
Методите, които приемат съставен форматиращ низ (composite formatting string) и променлив брой аргументи, изискват параметрите да се номерират и поставят във фигурни скоби, съдържащи незадължителен форматиращ низ. Ето пример:
Console.WriteLine("Здравейте, {0}!\n" +
"Днес е {1:dd.MM.yyyy, а часът е HH:mm:ss}.\n" +
"Вие сте посетител номер {2:D6}.\nДължите {3:C2}.",
"Бай Иван", DateTime.Now, 38, 17.40);
// Result:
// Здравейте, Бай Иван!
// Днес е 04.08.2004, а часът е 19:01:09.
// Вие сте посетител номер 000038.
// Дължите 17,40 лв.
|
В примера методът Console.WriteLine(…) се извиква с форматиращ низ и 4 параметъра, които се използват при форматирането.
Стандартните форматиращи низове позволяват задаването на изгледа при отпечатване на основните типове числови стойности. Форматиращият низ трябва да е във вида Axx, където А е символ от азбуката, наречен форматен спецификатор (format specifier), a xx е незадължително число между 0 и 99 (указател за точност), което задава броя на значимите цифри или броя на нулите след десетичната запетая. Форматиращият низ не трябва да съдържа празни места.
Форматният спецификатор трябва да е един от позволените форматиращи символи (по-важните от тях са изброени в таблицата по-долу). В противен случай се получава изключение от тип FormatException.
Спецификатор на форматиране
|
Име
|
Описание
|
C или c
|
валута
|
Числото се преобразува в низ, който представлява парична стойност в стандартния формат за валута на текущата нишка (ако не бъде зададен изрично друг формат за валута – NumberFormatInfo обект).
|
D или d
|
десетична стойност
|
Числото се преобразува в низ от десетични цифри (0-9), предхождани от минус, ако е отрицателно. Указателят за точност определя минималния брой цифри, присъстващи в получения низ. При необходимост се добавят нули от ляво. Този формат се поддържа само от целочислени типове.
|
E или e
|
научен (експоненциален)
|
Числото се преобразува в експоненциален формат. Например числото 3.14 се записва във вида 3,140000E+000. Указателят за точност определя броя на знаците след десетичната запетая.
|
F или f
|
фиксирана запетая
|
Подаденото реално число се записва като низ с фиксирана запетая, например "3,14". Указателят за точност определя броя на десетичните знаци. В зависимост от културата се използва различен разделител между цялата и дробната част ("." или ",").
|
N или n
|
число
|
Числото се преобразува в низ от вида "-d,ddd,ddd.ddd…", където всяко ‘d’ обозначава цифра (0-9). Низът започва със знака минус, ако числото е отрицателно. Между всяка група от три цифри в ляво от десетичната запетая се поставя разделител. Указателят за точност задава желания брой знаци след десетичната запетая.
|
P или p
|
процент
|
Числото се преобразува в проценти чрез умножение по 100. Указателят за точност задава желания брой десетични знаци.
|
X или x
|
шестнайсетично число
|
Числото се преобразува в низ от шестнайсетични цифри. Например числото 234 се записва като "EA". Това форматиране се поддържа само от целочислени типове.
|
Трябва да имаме предвид, че при повечето форматиращи низове крайният вид на низа зависи от настройките в Regional Options в Control Panel, когато не е подадена изрично култура. Класът NumberFormatInfo съдържа информация за валута, десетични разделители и други символи, свързани с числовите стойности. Ще го разгледаме по-подробно в секцията за интернационализация и култури.
Следните примери демонстрират форматирането на число във вид на валута, експоненциален запис, шестнайсетичен запис и процент.
int i = 100;
string formatted = i.ToString("C");
Console.WriteLine(formatted);
// Result: 100,00 лв
// (при български настройки на Windows)
double d = -3.27e38;
Console.WriteLine("{0:E}", d);
// Result: -3,270000E+038
int value = 29690;
Console.WriteLine("0x{0:X8}", value);
// Result: 0x000073FA
double p = 0.3378;
Console.WriteLine("{0:P}", p);
// Result: 33,78 %
|
В горния пример видът на получения низ зависи от регионалните настройки на машината, на която е стартиран.
Форматиране със собствени шаблони
При отпечатване на числа имаме възможност да задаваме и собствени шаблони за форматиране чрез използването на следните символи:
Форматиращ знак
|
Име
|
Описание
|
#
|
диез
|
Замества една цифра, като при липса на цифра не се отпечатва нищо.
|
0
|
нула
|
Замества една цифра, като при липса на цифра се отпечатва 0.
|
.
|
точка
|
Задава десетичната запетая.
|
,
|
запетая
|
Задава разделител между хилядите.
|
Следният пример показва как могат да се ползват тези символи за създаване на собствени шаблони:
long tel = 359888123456;
Console.WriteLine("Тел. {0:(+###) (##) ### ## ##}", tel);
// Result: Тел. (+359) (88) 812 34 56
decimal sum = 4317.60M;
Console.WriteLine("Sum = {0:###,###,##0.00}", sum);
// Result: Sum = 4 317,60
|
Както и в предходния пример, видът на получения низ зависи от регионалните настройки на машината, на която е стартиран. Те задават вида на десетичната точка, както и разделителите между хилядите.
Отместване при форматирането
При отпечатване с форматиране можем да задаваме ширината на полето, в което трябва да се запише резултатът. Ширината се указва след номера на аргумента, отделена със запетая. Например {0,10} разполага нулевият аргумент в пространство от 10 символа, като допълва отляво с интервали, а {0,-8} разполага нулевият аргумент в пространство от 8 символа, като допълва отдясно с интервали.
decimal sum = 4317.60M;
Console.WriteLine("Sum = |{0,16:C}|", sum);
// Result: Sum = | 4 317,60 лв|
string ip = "62.44.14.203";
Console.WriteLine("IP: |{0,-20}|", ip);
// Result: IP: |62.44.14.203 |
|
Освен чрез стандартните форматиращи низове, начинът на отпечатване на стойностите може да се контролира и чрез потребителско форматиране.
В горния пример, понеже се използва типът decimal, константата, която му се присвоява като стойност, трябва да завършва с буквата "M". В езика C# тази буква указва, че константната стойност е от тип System.Decimal. Ако тази буква се пропусне, компилаторът ще счита константата за число от тип double. По подобен начин константите от тип long трябва да завършват на "L", а константите от тип float – на "F".
Форматирането на дати и часове се извършва чрез шаблони за форматиране. Тези шаблони представляват символни низове, които се състоят от форматиращи символи (букви от латинската азбука, които се заместват с елемент от подадената дата или час) и други символи, които се използват без изменение. В таблицата са дадени най-често използваните форматиращи символи:
Форматиращ символ
|
Описание
|
d, dd
|
Замества деня от подадената дата (във вариант съответно с и без водеща нула).
|
M, MM
|
Замества месеца от подадената дата (във вариант съответно с и без водеща нула).
|
MMMM
|
Замества пълното име на месеца от подадената дата на езика, свързан с културата на текущата нишка.
|
yy, yyyy
|
Замества годината от подадената дата (в двуцифрен или четирицифрен формат).
|
h, hh, H, HH
|
Замества часа от подадените дата и време (във вариант съответно с и без водеща нула). Във варианта с малки букви се използва 12-часов формат, а във варианта с главни букви - 24-часов формат.
|
m, mm
|
Замества минутата от подадените и време (във вариант съответно с и без водеща нула).
|
s, ss
|
Замества секундата от подадените и време (във вариант съответно с и без водеща нула).
|
/
|
Замества текущия разделител между дни, месеци и години. Разделителят се взима от текущата култура (от свойството DateTimeFormatInfo.DateSeparator).
|
:
|
Замества текущия разделител между часове, минути и секунди. Разделителят се взима от текущата култура (от свойството DateTimeFormatInfo.TimeSeparator).
|
При отпечатване на дата може да се зададе или да не се зададе шаблон. Ако шаблон не бъде зададен, се използва шаблонът по подразбиране за текущата култура. От текущата култура зависят и някои от форматиращите символи, например разделителите.
Форматиране на дати и часове – пример
Ще демонстрираме използването на форматиращи низове за дати с няколко примера:
DateTime now = DateTime.Now;
Thread.CurrentThread.CurrentCulture=CultureInfo.InvariantCulture;
Console.WriteLine("{0:d.MM.yyyy - HH:mm:ss} ", now);
// Result: 28.09.2005 - 19:25:02
Thread.CurrentThread.CurrentCulture = new CultureInfo("bg-BG");
DateTime birthDate = new DateTime(1980, 06, 14, 06, 10, 00);
Console.WriteLine(
"Аз съм роден на {0:d MMMM yyyy г., в HH:mm часа}.",
birthDate);
// Result: Аз съм роден на 14 Юни 1980 г., в 06:10 часа.
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Console.WriteLine(DateTime.Now);
// Result: 9/28/2005 7:24:49 PM
| Потребителско форматиране
Типовете могат да дефинират свое собствено форматиране. За целта е нужно типът да имплементира интерфейса IFormattable и да предоставя свой метод ToString(string format, IFormatProvider formatProvider).
Възможно е също да си дефинираме собствен метод, който да се грижи за форматиране на произволен тип. За да направим наш метод Format(…), който да използваме за форматирането на даден тип, трябва да имплементираме ICustomProvider и IFormatProvider.
Потребителско форматиране за собствени типове
Можем да добавим възможност за обработка на дефинирани от нас форматиращи низове в дефинираните от нас собствени типове. За целта трябва в нашите типове да имплементираме интерфейса IFormattable и метода му ToString(). По този начин ще имаме контрол върху това какви форматиращи низове ще разпознава нашият тип.
Методът ToString(…) на IFormattable приема като параметри форматиращ низ и доставчик на формат (format provider). Ако форматиращият низ е празен или null, се извършва форматиране по подразбиране. Ако доставчикът на форматиране е null, се използва този по подразбиране.
Следва пример за тип, поддържащ няколко вида форматиращи низове:
class Money : IFormattable
{
private double mAmmount;
public Money(double aAmmount)
{
mAmmount = aAmmount;
}
public string ToString(string aFormat,
IFormatProvider aFormatProvider)
{
if (aFormat == "USD")
{
return String.Format(
"${0:###,###,##0.00}", mAmmount);
}
else if (aFormat == "BGL")
{
return String.Format(
"{0:###,###,##0.00} лв.", mAmmount);
}
else
{
throw new FormatException(String.Format(
"Invalid format: {0}", aFormat));
}
}
static void Main()
{
Money m = new Money(27000);
Console.WriteLine("В долари: {0:USD}", m);
// Result: В долари: $27 000,00
Console.WriteLine("В лева: {0:BGL}", m);
// Result: В лева: 27 000,00 лв.
try
{
Console.WriteLine("В евро: {0:EUR}", m);
}
catch (FormatException fe)
{
Console.WriteLine("Error: {0}", fe.Message);
}
// Result: Error: Invalid format: EUR
}
}
|
Класът Money от примера реализира интерфейса IFormattable и може да бъде форматиран с низовете "USD" и "BGL".
Потребителско форматиране за съществуващи типове
Можем да управляваме форматирането на вече съществуващите типове, като създаваме собствени класове, доставчици на формат. Те трябва да имплементират интерфейсите ICustomFormatter и IFormatProvider. При извикването на метода ToString(…), правилата, по които да ще се извърши форматирането, се определят от подадения като параметър доставчик на формат. Няма да се спираме не повече детайли. Конкретен пример за използването на ICustomFormatter и IFormatProvider има в MSDN Library, в помощната информация за класа IFormatProvider.
Сподели с приятели: |