فصل ۶: String، Text Encoding و Unicode در .NET
فروش یا انتشار این ترجمه منوط به داشتن مجوز لازم از صاحب حقوق اثر است.
۶. مبانی .NET
تصویر آغاز فصل ۶ در منبع.
بسیاری از امکانات اصلی که هنگام Programming به آنها نیاز دارید نه توسط خود زبان C#، بلکه توسط Typeهای موجود در BCL مربوط به .NET فراهم میشوند. در این فصل Typeهایی را پوشش میدهیم که در کارهای بنیادی Programming، مانند مقایسهٔ برابری مجازی، Order Comparison و Type Conversion کمک میکنند. همچنین Typeهای پایهٔ .NET مانند String، DateTime و Enum را بررسی میکنیم.
Typeهای این بخش در Namespace System قرار دارند، با استثناهای زیر:
StringBuilder در System.Text تعریف شده است؛ Typeهای مربوط به Text Encoding نیز در همین Namespace هستند.CultureInfo و Typeهای مرتبط در System.Globalization تعریف شدهاند.XmlConvert در System.Xml تعریف شده است.
کار با String و Text
Char
یک char در C# یک Character منفرد Unicode را نمایش میدهد و Aliasای برای Struct با نام System.Char است. در فصل ۲ توضیح دادیم Literalهای char را چگونه مینویسیم:
char c = 'A';
char newLine = '\n';
System.Char مجموعهای از Methodهای Static برای کار با Characterها، از جمله ToUpper، ToLower و IsWhiteSpace تعریف میکند. این Methodها را هم از طریق Type با نام System.Char و هم از طریق Alias آن یعنی char میتوانید فراخوانی کنید:
Console.WriteLine (System.Char.ToUpper ('c')); // C
Console.WriteLine (char.IsWhiteSpace ('\t')); // True
ToUpper و ToLower Locale مربوط به End User را رعایت میکنند و این موضوع میتواند به Bugهای ظریفی منجر شود. Expression زیر در Turkey مقدار false میدهد:
char.ToUpper ('i') == 'I'
دلیل این است که در Turkey، نتیجهٔ char.ToUpper ('i') برابر 'İ' است؛ به نقطهٔ بالای حرف توجه کنید. برای جلوگیری از این مشکل، System.Char و System.String نسخههای مستقل از Culture برای ToUpper و ToLower ارائه میکنند که نامشان با واژهٔ Invariant پایان مییابد. این نسخهها همیشه Ruleهای Culture انگلیسی را اعمال میکنند:
Console.WriteLine (char.ToUpperInvariant ('i')); // I
این عبارت Shortcut برای مورد زیر است:
Console.WriteLine (char.ToUpper ('i', CultureInfo.InvariantCulture))
برای اطلاعات بیشتر دربارهٔ Locale و Culture، بخش «Formatting and Parsing» در صفحهٔ 317 را ببینید.
بیشتر Methodهای Static باقیماندهٔ char به دستهبندی Characterها مربوطاند. Table 6-1 آنها را فهرست میکند.
جدول 6-1 — Methodهای Static برای دستهبندی Characterها| Static method | Characterهای شاملشده | Unicode Categoryهای شاملشده |
IsLetter | A–Z، a–z و حرفهای سایر Alphabetها | UpperCaseLetter
LowerCaseLetter
TitleCaseLetter
ModifierLetter
OtherLetter |
IsUpper | حرفهای Uppercase | UpperCaseLetter |
IsLower | حرفهای Lowercase | LowerCaseLetter |
IsDigit | 0–9 بههمراه Digitهای سایر Alphabetها | DecimalDigitNumber |
IsLetterOrDigit | Letterها بههمراه Digitها | (IsLetter، IsDigit) |
IsNumber | تمام Digitها بههمراه Fractionهای Unicode و Symbolهای Roman numeral | DecimalDigitNumber
LetterNumber
OtherNumber |
IsSeparator | Space بههمراه تمام Characterهای Separator در Unicode | LineSeparator
ParagraphSeparator |
IsWhiteSpace | تمام Separatorها بههمراه \n، \r، \t، \f و \v | LineSeparator
ParagraphSeparator |
IsPunctuation | Symbolهای مورد استفاده برای Punctuation در Alphabetهای غربی و دیگر Alphabetها | DashPunctuation
ConnectorPunctuation
InitialQuotePunctuation
FinalQuotePunctuation |
IsSymbol | بیشتر Symbolهای Printable دیگر | MathSymbol
ModifierSymbol
OtherSymbol |
IsControl | Characterهای «Control» غیرقابلچاپ پایینتر از 0x20 مانند \r، \n، \t، \0 و Characterهای بین 0x7F و 0x9A | (هیچکدام) |
برای دستهبندی ریزتر، char Methodی Static با نام GetUnicodeCategory ارائه میکند؛ این Method یک Enum از نوع UnicodeCategory برمیگرداند که Memberهای آن در ستون سمت راست Table 6-1 نمایش داده شدهاند.
یک char عرض 16 Bit دارد؛ این مقدار برای نمایش هر Character موجود در Basic Multilingual Plane یا BMP در Unicode کافی است. برای رفتن فراتر از BMP باید از Surrogate Pairها استفاده کنید؛ Methodهای مربوط در بخش «Text Encodings and Unicode» در صفحهٔ 301 توضیح داده میشوند.
String
یک string در C# که معادل System.String است، Sequenceای Immutable و غیرقابلتغییر از Characterهاست. در فصل ۲ نحوهٔ نوشتن String Literalها، اجرای Equality Comparison و Concatenate کردن دو String را توضیح دادیم. این بخش Functionهای دیگر کار با String را پوشش میدهد که از طریق Memberهای Static و Instance کلاس System.String در دسترساند.
ساختن Stringها
سادهترین راه ساخت String، Assign کردن یک Literal است، همانطور که در فصل ۲ دیدیم:
string s1 = "Hello";
string s2 = "First Line\r\nSecond Line";
string s3 = @"\\server\fileshare\helloworld.cs";
برای ساخت Sequence تکراری از Characterها میتوانید از Constructor مربوط به string استفاده کنید:
Console.Write (new string ('*', 10)); // **********
همچنین میتوانید از یک Array از char، String بسازید. Method با نام ToCharArray عملیات معکوس را انجام میدهد:
char[] ca = "Hello".ToCharArray();
string s = new string (ca); // s = "Hello"
Constructor مربوط به string برای پذیرش Pointer Typeهای مختلف و Unsafe نیز Overload شده است تا بتوان از Typeهایی مانند char* String ساخت.
Stringهای Null و Empty
String خالی Length برابر صفر دارد. برای ایجاد String خالی میتوانید از Literal خالی یا Field Static با نام string.Empty استفاده کنید؛ برای Test کردن Empty بودن String نیز یا Equality Comparison انجام دهید یا Property با نام Length را بررسی کنید:
string empty = "";
Console.WriteLine (empty == ""); // True
Console.WriteLine (empty == string.Empty); // True
Console.WriteLine (empty.Length == 0); // True
از آنجا که Stringها Reference Type هستند، میتوانند null نیز باشند:
string nullString = null;
Console.WriteLine (nullString == null); // True
Console.WriteLine (nullString == ""); // False
Console.WriteLine (nullString.Length == 0); // NullReferenceException
Method Static با نام string.IsNullOrEmpty Shortcut مفیدی برای Test کردن این است که String دادهشده null یا Empty باشد.
دسترسی به Characterهای درون String
Indexer مربوط به String یک Character منفرد را در Index دادهشده برمیگرداند. مانند تمام Functionهایی که روی String کار میکنند، Index از صفر آغاز میشود:
string str = "abcde";
char letter = str[1]; // letter == 'b'
string همچنین IEnumerable<char> را Implement میکند، بنابراین میتوانید روی Characterهای آن با foreach پیمایش کنید:
foreach (char c in "123") Console.Write (c + ","); // 1,2,3,
جستوجو در Stringها
سادهترین Methodهای جستوجو در Stringها StartsWith، EndsWith و Contains هستند. همهٔ آنها true یا false برمیگردانند:
Console.WriteLine ("quick brown fox".EndsWith ("fox")); // True
Console.WriteLine ("quick brown fox".Contains ("brown")); // True
این Methodها Overloadهایی دارند که اجازه میدهد یک Enum از نوع StringComparison مشخص کنید تا حساسیت به Case و Culture کنترل شود؛ بخش «Ordinal versus culture comparison» در صفحهٔ 297 را ببینید. حالت پیشفرض، Match حساس به Case با Ruleهای Culture جاری و Localized است. عبارت زیر در عوض، Search غیرحساس به Case را با Ruleهای Invariant Culture انجام میدهد:
"abcdef".StartsWith ("aBc", StringComparison.InvariantCultureIgnoreCase)
IndexOf اولین Position مربوط به Character یا Substring دادهشده را برمیگرداند، یا اگر Substring پیدا نشود مقدار -1:
Console.WriteLine ("abcde".IndexOf ("cd")); // 2
IndexOf برای پذیرش startPosition، یعنی Index شروع Search، و همچنین Enum از نوع StringComparison نیز Overload شده است:
Console.WriteLine ("abcde abcde".IndexOf ("CD", 6,
StringComparison.CurrentCultureIgnoreCase)); // 8
LastIndexOf شبیه IndexOf است، اما در String بهسمت عقب حرکت میکند.
IndexOfAny اولین Position مطابق با یکی از Characterهای یک Set را برمیگرداند:
Console.Write ("ab,cd ef".IndexOfAny (new char[] {' ', ','} )); // 2
Console.Write ("pas5w0rd".IndexOfAny ("0123456789".ToCharArray() )); // 3
LastIndexOfAny همین کار را در جهت معکوس انجام میدهد.
دستکاری Stringها
چون String Immutable است، تمام Methodهایی که String را «دستکاری» میکنند یک String جدید برمیگردانند و String اصلی را دستنخورده باقی میگذارند؛ Assign مجدد یک Variable از نوع String نیز همین وضعیت را دارد.
Substring بخشی از String را Extract میکند:
string left3 = "12345".Substring (0, 3); // left3 = "123";
string mid3 = "12345".Substring (1, 3); // mid3 = "234";
اگر Length را حذف کنید، باقی String را دریافت میکنید:
string end3 = "12345".Substring (2); // end3 = "345";
Insert و Remove بهترتیب Characterها را در Position مشخص Insert یا Remove میکنند:
string s1 = "helloworld".Insert (5, ", "); // s1 = "hello, world"
string s2 = s1.Remove (5, 2); // s2 = "helloworld";
PadLeft و PadRight یک String را تا Length مشخص با Character دادهشده Pad میکنند؛ اگر Character مشخص نشود از Space استفاده میشود:
Console.WriteLine ("12345".PadLeft (9, '*')); // ****12345
Console.WriteLine ("12345".PadLeft (9)); // 12345
اگر Input String از Padding Length بلندتر باشد، String اصلی بدون تغییر برگردانده میشود.
TrimStart و TrimEnd Characterهای مشخصشده را از ابتدا یا انتهای String حذف میکنند؛ Trim هر دو کار را انجام میدهد. بهصورت پیشفرض این Functionها Whitespace را حذف میکنند، از جمله Space، Tab، New Line و Variationهای Unicode آنها:
Console.WriteLine (" abc \t\r\n ".Trim().Length); // 3
Replace تمام Occurrenceهای غیرهمپوشان یک Character یا Substring مشخص را جایگزین میکند:
Console.WriteLine ("to be done".Replace (" ", " | ") ); // to | be | done
Console.WriteLine ("to be done".Replace (" ", "") ); // tobedone
ToUpper و ToLower نسخهٔ Uppercase و Lowercase Input String را برمیگردانند. بهطور پیشفرض Settings زبان جاری User را رعایت میکنند؛ ToUpperInvariant و ToLowerInvariant همیشه Ruleهای Alphabet انگلیسی را اعمال میکنند.
Split و Join کردن Stringها
Split یک String را به قسمتهای مختلف تقسیم میکند:
string[] words = "The quick brown fox".Split();
foreach (string word in words)
Console.Write (word + "|"); // The|quick|brown|fox|
بهصورت پیشفرض، Split از Characterهای Whitespace بهعنوان Delimiter استفاده میکند؛ همچنین Overloadهایی برای پذیرش Array از نوع params char یا string بهعنوان Delimiter دارد. Split میتواند بهصورت اختیاری Enum از نوع StringSplitOptions بگیرد که Optionای برای حذف Entryهای Empty دارد؛ این Option وقتی Wordها با چند Delimiter پشتسرهم جدا شدهاند مفید است.
Method Static با نام Join کار معکوس Split را انجام میدهد. به Delimiter و Array از String نیاز دارد:
string[] words = "The quick brown fox".Split();
string together = string.Join (" ", words); // The quick brown fox
Method Static با نام Concat شبیه Join است، اما فقط Array از نوع params string میپذیرد و Separator اعمال نمیکند. Concat دقیقاً معادل Operator با علامت + است؛ Compiler در واقع + را به Concat ترجمه میکند:
string sentence = string.Concat ("The", " quick", " brown", " fox");
string sameSentence = "The" + " quick" + " brown" + " fox";
String.Format و Composite Format Stringها
Method Static با نام Format راهی راحت برای ساخت Stringهایی است که Variableها را در خود Embed میکنند. Variableها یا Valueهای Embedشده میتوانند از هر Typeی باشند؛ Format صرفاً ToString را روی آنها فراخوانی میکند.
String اصلی که Variableهای Embedشده را دربر دارد Composite Format String نامیده میشود. هنگام فراخوانی String.Format، ابتدا Composite Format String و سپس هر یک از Variableهای Embedشده را میدهید:
string composite = "It's {0} degrees in {1} on this {2} morning";
string s = string.Format (composite, 35, "Perth", DateTime.Now.DayOfWeek);
// s == "It's 35 degrees in Perth on this Friday morning"
(و این دما بر حسب Celsius است!)
با Interpolated String Literalها نیز میتوانیم همین اثر را ایجاد کنیم؛ بخش «String Type» در صفحهٔ 58 را ببینید. کافی است پیش از String علامت $ بگذارید و Expressionها را داخل Brace قرار دهید:
string s = $"It's hot this {DateTime.Now.DayOfWeek} morning";
هر Number داخل Brace یک Format Item نام دارد. Number متناظر با Position Argument است و بهصورت اختیاری میتواند بهدنبال خود موارد زیر را داشته باشد:
- یک Comma و Minimum Width مورد نظر.
- یک Colon و Format String.
Minimum Width برای همتراز کردن Columnها مفید است. اگر Value منفی باشد Data به چپ Align میشود؛ در غیر این صورت به راست Align میشود:
string composite = "Name={0,-20} Credit Limit={1,15:C}";
Console.WriteLine (string.Format (composite, "Mary", 500));
Console.WriteLine (string.Format (composite, "Elizabeth", 20000));
نتیجه:
Name=Mary Credit Limit= $500.00
Name=Elizabeth Credit Limit= $20,000.00
معادل آن بدون استفاده از string.Format:
string s = "Name=" + "Mary".PadRight (20) +
" Credit Limit=" + 500.ToString ("C").PadLeft (15);
Credit Limit بهدلیل Format String با مقدار "C" به شکل Currency Format شده است. Format Stringها در بخش «Formatting and Parsing» در صفحهٔ 317 با جزئیات توضیح داده میشوند.
مقایسهٔ Stringها
هنگام مقایسهٔ دو Value، .NET بین مفهوم Equality Comparison و Order Comparison تفاوت قائل میشود. Equality Comparison بررسی میکند آیا دو Instance از نظر معنا یکساناند؛ Order Comparison تعیین میکند هنگام چیدن Valueها بهترتیب صعودی یا نزولی، کدامیک از دو Instance زودتر میآید.
برای Equality Comparison در String میتوانید از Operator با علامت == یا یکی از Methodهای Equals مربوط به string استفاده کنید. Methodهای Equals انعطافپذیرتر هستند، زیرا Optionهایی مانند Case Insensitivity را میپذیرند.
برای Order Comparison در String میتوانید از Instance Method با نام CompareTo یا Methodهای Static با نام Compare و CompareOrdinal استفاده کنید. این Methodها بسته به اینکه Value اول بعد از Value دوم، قبل از آن یا همجای آن قرار گیرد، Number مثبت، منفی یا صفر برمیگردانند.
پیش از ورود به جزئیات هر یک، باید Algorithmهای زیربنایی .NET برای مقایسهٔ String را بررسی کنیم.
مقایسهٔ Ordinal در برابر Culture-sensitive
دو Algorithm پایه برای String Comparison وجود دارد: Ordinal و Culture-sensitive. مقایسهٔ Ordinal، Characterها را صرفاً بهصورت Number و بر اساس Value عددی Unicode آنها تفسیر میکند؛ Comparison حساس به Culture، Characterها را با توجه به Alphabet مشخصی تفسیر میکند. دو Culture ویژه وجود دارد: «Current Culture» که از Settings انتخابشده در Control Panel Computer گرفته میشود، و Invariant Culture.
Invariant Culture روی همهٔ Computerها یکسان است و شباهت زیادی به Culture آمریکایی دارد.
برای Equality Comparison، هر دو Algorithm از نوع Ordinal و Culture-specific مفیدند. اما برای Ordering، Comparison بر اساس Culture تقریباً همیشه مناسبتر است، زیرا برای مرتب کردن Alphabetical Stringها به Alphabet نیاز دارید. Ordinal به Valueهای عددی Unicode متکی است که تصادفاً Characterهای انگلیسی را تقریباً Alphabetical مرتب میکند، اما حتی در همین حالت نیز نتیجه کاملاً مطابق انتظار نیست. برای مثال با فرض Case Sensitivity، Stringهای "Atom"، "atom" و "Zamia" را در نظر بگیرید. Invariant Culture آنها را چنین مرتب میکند:
"atom", "Atom", "Zamia"
Ordinal در عوض ترتیب زیر را ایجاد میکند:
"Atom", "Zamia", "atom"
دلیل این است که Invariant Culture یک Alphabet را در خود دارد که Characterهای Uppercase را کنار همتای Lowercase آنها قرار میدهد، مانند aAbBcCdD.... Algorithm از نوع Ordinal، در مقابل، ابتدا همهٔ Characterهای Uppercase و سپس همهٔ Characterهای Lowercase را قرار میدهد: A...Z, a...z. این در اصل یادگار Character Set با نام ASCII است که در دههٔ 1960 اختراع شد.
Equality Comparison در String
با وجود محدودیتهای Ordinal، Operator با علامت == در string همیشه Comparison از نوع Ordinal و حساس به Case انجام میدهد. همین موضوع برای نسخهٔ Instance از string.Equals در حالتی که بدون Argument فراخوانی شود نیز صادق است و این رفتار، Equality Comparison «پیشفرض» Type با نام string را تعریف میکند.
Methodهای زیر امکان Comparison آگاه از Culture یا غیرحساس به Case را فراهم میکنند:
public bool Equals (string value, StringComparison comparisonType);
public static bool Equals (string a, string b,
StringComparison comparisonType);
نسخهٔ Static این مزیت را دارد که حتی اگر یکی یا هر دو String مقدار null داشته باشند نیز کار میکند. StringComparison Enumای است که بهشکل زیر تعریف شده است:
public enum StringComparison
{
CurrentCulture, // Case-sensitive
CurrentCultureIgnoreCase,
InvariantCulture, // Case-sensitive
InvariantCultureIgnoreCase,
Ordinal, // Case-sensitive
OrdinalIgnoreCase
}
برای مثال:
Console.WriteLine (string.Equals ("foo", "FOO",
StringComparison.OrdinalIgnoreCase)); // True
Console.WriteLine ("ṻ" == "ǖ"); // False
Console.WriteLine (string.Equals ("ṻ", "ǖ",
StringComparison.CurrentCulture)); // ?
نتیجهٔ Example سوم با Settings زبان فعلی Computer تعیین میشود.
Order Comparison در String
Instance Method با نام CompareTo در String، Order Comparison حساس به Culture و حساس به Case انجام میدهد. برخلاف Operator با علامت ==، CompareTo از Comparison از نوع Ordinal استفاده نمیکند؛ برای Ordering، Algorithm حساس به Culture بسیار مفیدتر است. تعریف Method بهصورت زیر است:
public int CompareTo (string strB);
برای انواع دیگر Comparison میتوانید Methodهای Static با نام Compare و CompareOrdinal را فراخوانی کنید:
public static int Compare (string strA, string strB,
StringComparison comparisonType);
public static int Compare (string strA, string strB, bool ignoreCase,
CultureInfo culture);
public static int Compare (string strA, string strB, bool ignoreCase);
public static int CompareOrdinal (string strA, string strB);
دو Method آخر صرفاً Shortcut برای فراخوانی دو Method نخستاند.
تمام Methodهای Order Comparison بسته به اینکه Value اول بعد از Value دوم، قبل از آن یا همجای آن باشد، Number مثبت، منفی یا صفر برمیگردانند:
Console.WriteLine ("Boston".CompareTo ("Austin")); // 1
Console.WriteLine ("Boston".CompareTo ("Boston")); // 0
Console.WriteLine ("Boston".CompareTo ("Chicago")); // -1
Console.WriteLine ("ṻ".CompareTo ("ǖ")); // 1
Console.WriteLine ("foo".CompareTo ("FOO")); // -1
عبارت زیر Comparison غیرحساس به Case را با Current Culture انجام میدهد:
Console.WriteLine (string.Compare ("foo", "FOO", true)); // 0
با ارائهٔ Object از نوع CultureInfo میتوانید هر Alphabet دلخواه را وارد Comparison کنید:
// CultureInfo is defined in the System.Globalization namespace
CultureInfo german = CultureInfo.GetCultureInfo ("de-DE");
int i = string.Compare ("Müller", "Muller", false, german);
StringBuilder
کلاس StringBuilder در Namespace System.Text یک String Mutable یا قابلویرایش را نمایش میدهد. با StringBuilder میتوانید Substringها را با Append، Insert، Remove و Replace تغییر دهید، بدون اینکه کل StringBuilder را جایگزین کنید.
Constructor مربوط به StringBuilder بهصورت اختیاری یک Value اولیه از نوع String و همچنین Size شروع برای Capacity داخلی میپذیرد؛ مقدار پیشفرض Capacity برابر 16 Character است. اگر از این مقدار فراتر بروید، StringBuilder Structureهای داخلی خود را بهصورت خودکار Resize میکند تا با هزینهٔ Performance اندک تا Maximum Capacity خود که پیشفرض آن int.MaxValue است، فضا فراهم شود.
یکی از کاربردهای رایج StringBuilder، ساخت تدریجی یک String بلند با فراخوانی مکرر Append است. این روش از Concatenate کردن مکرر Type معمولی string بسیار پربازدهتر است:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 50; i++) sb.Append(i).Append(",");
برای دریافت Result نهایی، ToString() را فراخوانی کنید:
Console.WriteLine (sb.ToString());
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,
27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,
AppendLine یک Append انجام میدهد که Sequence مربوط به New Line را نیز اضافه میکند؛ در Windows این Sequence برابر "\r\n" است. AppendFormat مانند String.Format یک Composite Format String میپذیرد.
علاوه بر Methodهای Insert، Remove و Replace که Replace مانند string.Replace عمل میکند، StringBuilder Property با نام Length و Indexer قابلنوشتن برای Get/Set کردن Characterهای منفرد دارد.
برای پاک کردن محتویات StringBuilder، یا یک Instance جدید بسازید یا Length آن را صفر کنید.
Text Encoding و Unicode
Character Set تخصیصی از Characterهاست که هر کدام Code عددی یا Code Point دارند. دو Character Set رایج Unicode و ASCII هستند. Unicode Address Spaceای با حدود یک میلیون Character دارد که نزدیک به 100,000 مورد آن در حال حاضر تخصیص یافتهاند. Unicode بیشتر زبانهای گفتاری جهان، برخی زبانهای تاریخی و Symbolهای ویژه را پوشش میدهد. مجموعهٔ ASCII صرفاً 128 Character اول Unicode است و بیشتر آنچه روی Keyboard به سبک آمریکا میبینید را پوشش میدهد. ASCII حدود 30 سال پیش از Unicode وجود داشته و هنوز گاهی بهدلیل سادگی و Efficiency استفاده میشود؛ هر Character با یک Byte نمایش داده میشود.
Type System مربوط به .NET برای کار با Character Set با نام Unicode طراحی شده است. ASCII نیز بهدلیل اینکه Subsetای از Unicode است بهصورت ضمنی پشتیبانی میشود.
Text Encoding، Characterها را از Code Point عددی آنها به Representation باینری Map میکند. در .NET، Text Encoding بیشتر هنگام کار با Text File یا Stream مطرح میشود. وقتی Text File را در یک String میخوانید، Text Encoder، Data مربوط به File را از Binary به Representation داخلی Unicode که Typeهای char و string انتظار دارند تبدیل میکند. Text Encoding میتواند Characterهای قابلنمایش را محدود کند و بر Efficiency ذخیرهسازی نیز اثر بگذارد.
در .NET دو دسته Text Encoding وجود دارد:
- Encodingهایی که Characterهای Unicode را به Character Set دیگری Map میکنند.
- Encodingهایی که از Schemeهای استاندارد Encoding در Unicode استفاده میکنند.
دستهٔ نخست شامل Encodingهای Legacy مانند EBCDIC متعلق به IBM و Character Setهای 8-bit با Characterهای توسعهیافته در نیمهٔ بالایی 128 Value است که پیش از Unicode محبوب بودند و با Code Page شناخته میشوند. ASCII Encoding نیز در همین دسته قرار دارد: 128 Character اول را Encode میکند و بقیه را کنار میگذارد. این دسته همچنین GB18030 غیرLegacy را شامل میشود که از سال 2000 Standard اجباری برای Applicationهایی است که در China نوشته یا به China فروخته میشوند.
دستهٔ دوم شامل UTF-8، UTF-16 و UTF-32 و نیز UTF-7 منسوخ است. هر کدام از نظر Efficiency فضا متفاوتاند. UTF-8 برای بیشتر انواع Text کمحجمترین است و برای هر Character بین یک تا چهار Byte مصرف میکند. 128 Character اول فقط یک Byte نیاز دارند و بنابراین با ASCII سازگارند. UTF-8 محبوبترین Encoding برای Text File و Stream، بهخصوص در Internet، است و Encoding پیشفرض برای I/O مربوط به Stream در .NET محسوب میشود؛ در واقع تقریباً برای هر چیزی که بهصورت ضمنی از Encoding استفاده کند نیز پیشفرض است.
UTF-16 برای نمایش هر Character یک یا دو Word با اندازهٔ 16 Bit استفاده میکند. .NET در داخل برای نمایش Character و String از همین Encoding استفاده میکند. برخی Programها نیز File را با UTF-16 مینویسند.
UTF-32 کمصرفهترین Encoding از نظر فضاست: هر Code Point را مستقیماً به 32 Bit Map میکند، بنابراین هر Character چهار Byte مصرف میکند. به همین دلیل UTF-32 بهندرت استفاده میشود. در عوض Random Access را بسیار ساده میکند، زیرا همهٔ Characterها تعداد Byte برابر دارند.
گرفتن Object از نوع Encoding
کلاس Encoding در System.Text Base Type مشترک کلاسهایی است که Text Encodingها را Encapsulate میکنند. چند Subclass دارد که هدفشان Encapsulate کردن Familyهایی از Encoding با Featureهای مشابه است. رایجترین Encodingها از Propertyهای Static اختصاصی روی Encoding قابل دریافتاند:
| نام Encoding | Static Property روی Encoding |
|---|
| UTF-8 | Encoding.UTF8 |
| UTF-16 | Encoding.Unicode (نه UTF16) |
| UTF-32 | Encoding.UTF32 |
| ASCII | Encoding.ASCII |
Encodingهای دیگر را میتوانید با فراخوانی Encoding.GetEncoding و ارائهٔ نام استاندارد Character Set از Internet Assigned Numbers Authority یا IANA دریافت کنید:
// In .NET 5+ and .NET Core, you must first call RegisterProvider:
Encoding.RegisterProvider (CodePagesEncodingProvider.Instance);
Encoding chinese = Encoding.GetEncoding ("GB18030");
Method Static با نام GetEncodings فهرستی از تمام Encodingهای پشتیبانیشده را همراه نام استاندارد IANA آنها برمیگرداند:
foreach (EncodingInfo info in Encoding.GetEncodings())
Console.WriteLine (info.Name);
راه دیگر برای گرفتن Encoding این است که مستقیماً یک Encoding Class را Instantiate کنید. این کار اجازه میدهد Optionهای گوناگونی را با Argumentهای Constructor تنظیم کنید، از جمله:
- آیا هنگام Decode شدن Byte Sequence نامعتبر Exception رخ دهد یا نه. پیشفرض
false است. - آیا UTF-16/UTF-32 با Byteهای پرارزشتر در ابتدا یعنی Big Endian یا کمارزشتر در ابتدا یعنی Little Endian Encode/Decode شود. پیشفرض Little Endian است که Standard در Windows Operating System محسوب میشود.
- آیا Byte-order Mark، یعنی Prefix نشاندهندهٔ Endianness، Emit شود یا نه.
Encoding برای File و Stream I/O
رایجترین کاربرد Object از نوع Encoding، کنترل شیوهٔ خواندن و نوشتن Text در File یا Stream است. برای مثال کد زیر عبارت Testing... را با Encoding از نوع UTF-16 در File با نام data.txt مینویسد:
System.IO.File.WriteAllText ("data.txt", "Testing...", Encoding.Unicode);
اگر Argument آخر را حذف کنید، WriteAllText از UTF-8 رایج استفاده میکند.
این موضوع در فصل ۱۵ و بخش «Stream Adapters» در صفحهٔ 709 ادامه مییابد.
Encode کردن به Byte Array
میتوانید از Object از نوع Encoding برای رفتوبرگشت به Byte Array نیز استفاده کنید. Method با نام GetBytes با Encoding دادهشده از string به byte[] تبدیل میکند و GetString از byte[] به string:
byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes ("0123456789");
byte[] utf16Bytes = System.Text.Encoding.Unicode.GetBytes ("0123456789");
byte[] utf32Bytes = System.Text.Encoding.UTF32.GetBytes ("0123456789");
Console.WriteLine (utf8Bytes.Length); // 10
Console.WriteLine (utf16Bytes.Length); // 20
Console.WriteLine (utf32Bytes.Length); // 40
string original1 = System.Text.Encoding.UTF8.GetString (utf8Bytes);
string original2 = System.Text.Encoding.Unicode.GetString (utf16Bytes);
string original3 = System.Text.Encoding.UTF32.GetString (utf32Bytes);
Console.WriteLine (original1); // 0123456789
Console.WriteLine (original2); // 0123456789
Console.WriteLine (original3); // 0123456789
UTF-16 و Surrogate Pairها
به یاد داشته باشید که .NET، Characterها و Stringها را در UTF-16 ذخیره میکند. چون UTF-16 برای هر Character به یک یا دو Word با اندازهٔ 16 Bit نیاز دارد و char فقط 16 Bit طول دارد، بعضی Characterهای Unicode به دو char برای نمایش نیاز دارند. این موضوع چند پیامد دارد:
- Property با نام
Length در String ممکن است از Count واقعی Characterها بزرگتر باشد. - یک
char منفرد همیشه برای نمایش کامل یک Character Unicode کافی نیست.
بیشتر Applicationها این موضوع را نادیده میگیرند، زیرا تقریباً تمام Characterهای رایج در بخشی از Unicode به نام Basic Multilingual Plane یا BMP جا میگیرند که در UTF-16 فقط یک Word 16-bit نیاز دارد. BMP چند ده زبان دنیا را پوشش میدهد.
BMP همچنین بیش از 30,000 Character چینی را شامل میشود. موارد خارج از آن شامل Characterهای برخی زبانهای باستانی، Symbolهای Musical Notation، بعضی Characterهای کمکاربرد چینی و بیشتر Emojiها هستند.
اگر لازم است از Characterهای دوWordی پشتیبانی کنید، Methodهای Static زیر در char یک Code Point با اندازهٔ 32 Bit را به Stringی شامل دو char تبدیل میکنند و بالعکس:
string ConvertFromUtf32 (int utf32)
int ConvertToUtf32 (char highSurrogate, char lowSurrogate)
Characterهای دوWordی Surrogate نامیده میشوند. تشخیص آنها آسان است، زیرا هر Word در Range برابر 0xD800 تا 0xDFFF قرار دارد. Methodهای Static زیر در char برای کمک به این کار وجود دارند:
bool IsSurrogate (char c)
bool IsHighSurrogate (char c)
bool IsLowSurrogate (char c)
bool IsSurrogatePair (char highSurrogate, char lowSurrogate)
کلاس StringInfo در Namespace System.Globalization نیز مجموعهای از Methodها و Propertyها برای کار با Characterهای دوWordی ارائه میکند.
Characterهای خارج از BMP معمولاً به Fontهای ویژه نیاز دارند و Operating Systemها پشتیبانی محدودی از آنها دارند.
تاریخها و زمانها
Structهای Immutable زیر در Namespace System کار نمایش Date و Time را انجام میدهند:
DateTime, DateTimeOffset, TimeSpan, DateOnly, TimeOnly
C# Keyword ویژهای که مستقیماً به این Typeها Map شود تعریف نمیکند.
TimeSpan
TimeSpan یک Interval زمانی یا Time of Day را نمایش میدهد. در نقش دوم، صرفاً «Clock Time» بدون Date است که با مدت گذشته از Midnight برابر است، با فرض اینکه Transition مربوط به Daylight Saving رخ ندهد. TimeSpan Resolution برابر 100 ns دارد، Maximum Value آن حدود 10 میلیون Day است و میتواند مثبت یا منفی باشد.
سه راه برای ساخت TimeSpan وجود دارد:
- استفاده از یکی از Constructorها.
- فراخوانی یکی از Methodهای Static از خانوادهٔ
From.... - کم کردن یک
DateTime از DateTime دیگر.