مقدمه

در درس قبل، دیدیم که استریم‌ها (Streams) با دنباله‌ای از بایت‌ها کار می‌کنند، در حالی که برنامه‌های ما اغلب با داده‌های متنی (رشته‌ها و کاراکترها) سروکار دارند. کلاس‌های کمکی مانند StreamReader و StreamWriter این فرآیند تبدیل را برای ما ساده می‌کنند، اما در پشت صحنه، یک عملیات بسیار مهم در حال انجام است: انکدینگ (Encoding) و دکدینگ (Decoding). کامپیوترها متن را به همان شکلی که ما می‌بینیم، ذخیره نمی‌کنند؛ آن‌ها فقط با اعداد (بایت‌ها) کار می‌کنند. انکدینگ، فرآیند تبدیل کاراکترهای متنی به دنباله‌ای از بایت‌ها برای ذخیره‌سازی یا انتقال است. دکدینگ، فرآیند معکوس یعنی تبدیل آن دنباله‌ی بایت‌ها به کاراکترهای قابل خواندن است.

درک مفهوم انکدینگ برای کار صحیح با فایل‌های متنی، به خصوص در یک دنیای چندزبانه، بسیار حیاتی است. انتخاب انکدینگ اشتباه می‌تواند منجر به نمایش نادرست کاراکترها (که اغلب به صورت علامت سؤال یا کاراکترهای عجیب و غریب دیده می‌شود) و از دست رفتن داده‌ها گردد.

کلاس System.Text.Encoding

کلاس انتزاعی System.Text.Encoding کلاس پایه‌ای است که تمام استانداردهای انکدینگ در .NET از آن ارث‌بری می‌کنند. این کلاس متدهای اصلی GetBytes() (برای انکد کردن یک رشته به آرایه‌ای از بایت‌ها) و GetString() (برای دکد کردن آرایه‌ای از بایت‌ها به یک رشته) را تعریف می‌کند.

.NET چندین پیاده‌سازی استاندارد از این کلاس را به صورت پراپرتی‌های استاتیک فراهم می‌کند:

  • Encoding.UTF8: رایج‌ترین و استانداردترین انکدینگ برای وب و فایل‌های متنی مدرن. این انکدینگ از تمام کاراکترهای یونیکد پشتیبانی می‌کند و برای کاراکترهای انگلیسی (ASCII) بسیار بهینه است. این انکدینگ، انتخاب پیش‌فرض برای بیشتر کلاس‌های StreamWriter و StreamReader است.
  • Encoding.Unicode: معادل انکدینگ UTF-16 است. هر کاراکتر را با دو یا چهار بایت نمایش می‌دهد.
  • Encoding.ASCII: یک انکدینگ قدیمی ۷ بیتی که فقط ۱۲۸ کاراکتر اصلی انگلیسی را پشتیبانی می‌کند و قادر به نمایش کاراکترهای زبان‌های دیگر (مانند فارسی) نیست.

انکدینگ و دکدینگ به صورت عملی

بیایید ببینیم چگونه می‌توانیم یک رشته را به بایت تبدیل کرده و سپس آن را برگردانیم.

Copy Icon Program.cs
using System.Text;

string originalString = "سلام C#!";
Console.WriteLine($"Original string: {originalString}");

// 1. Encode the string to a byte array using UTF-8.
Encoding utf8 = Encoding.UTF8;
byte[] encodedBytes = utf8.GetBytes(originalString);

Console.WriteLine("\nEncoded bytes (UTF-8):");
Console.WriteLine(BitConverter.ToString(encodedBytes));

// 2. Decode the byte array back to a string.
string decodedString = utf8.GetString(encodedBytes);
Console.WriteLine($"\nDecoded string: {decodedString}");

در این مثال، ما ابتدا رشته‌ی فارسی خود را با استفاده از Encoding.UTF8 به دنباله‌ای از بایت‌ها تبدیل می‌کنیم. سپس همان بایت‌ها را با استفاده از همان انکدینگ، به رشته‌ی اصلی برمی‌گردانیم.

اهمیت استفاده از انکدینگ صحیح

مشکل زمانی رخ می‌دهد که شما داده‌ای را با یک انکدینگ می‌نویسید، اما با انکدینگ دیگری تلاش به خواندن آن می‌کنید.

Copy Icon Program.cs
string originalString = "€ 25"; // Using the Euro symbol

// Encode with UTF-8
byte[] utf8Bytes = Encoding.UTF8.GetBytes(originalString);

// Now, try to decode using the wrong encoding (ASCII)
Encoding ascii = Encoding.ASCII;
string wrongString = ascii.GetString(utf8Bytes);

Console.WriteLine($"Original:  {originalString}");
Console.WriteLine($"Decoded with wrong encoding: {wrongString}"); // Output: ?? 25

همانطور که می‌بینید، چون انکدینگ ASCII نمی‌داند که بایت‌های مربوط به علامت یورو (€) چه هستند، آن را با علامت سؤال جایگزین می‌کند و داده‌ی ما از بین می‌رود. این نشان می‌دهد که هماهنگی بین انکدینگ در زمان نوشتن و خواندن چقدر حیاتی است.

مشخص کردن انکدینگ در StreamReader و StreamWriter

شما می‌توانید انکدینگ مورد نظر خود را به عنوان یک پارامتر به سازنده‌ی کلاس‌های StreamReader و StreamWriter ارسال کنید.

Copy Icon Program.cs
// Write a file using Unicode (UTF-16) encoding.
using (StreamWriter writer = new StreamWriter("myUnicodeFile.txt", append: false, encoding: Encoding.Unicode))
{
    writer.WriteLine("This is a Unicode file.");
}

// Read the file, specifying the correct encoding.
using (StreamReader reader = new StreamReader("myUnicodeFile.txt", encoding: Encoding.Unicode))
{
    Console.WriteLine(reader.ReadToEnd());
}

Byte Order Mark (BOM)

برخی از انکدینگ‌ها (به خصوص انکدینگ‌های خانواده‌ی یونیکد مانند UTF-8 و UTF-16) می‌توانند چند بایت اضافی را در ابتدای استریم یا فایل قرار دهند. این بایت‌ها که به آن‌ها Byte Order Mark (BOM) گفته می‌شود، مانند یک امضای مخفی عمل کرده و به برنامه‌هایی که فایل را می‌خوانند، کمک می‌کنند تا انکدینگ صحیح را به طور خودکار تشخیص دهند.

کلاس StreamReader در .NET به طور هوشمندانه می‌تواند وجود BOM را تشخیص دهد. اگر یک فایل با BOM ذخیره شده باشد، StreamReader معمولاً می‌تواند انکدینگ آن را به درستی تشخیص دهد، حتی اگر شما به صراحت آن را مشخص نکرده باشید. با این حال، همیشه بهترین رویه این است که در صورت امکان، انکدینگ را به طور صریح مشخص کنید تا از هرگونه ابهام جلوگیری شود.