A szöveges fájlok kezelésénél volt szó róla, hogy valójában a szöveges fájlok csak egy speciális fajtái a bináris fájloknak, amihez mi emberek társÃtunk jelentést. A számÃtógép szempontjából azonban a byte az byte, amivel a megjelenÃtÅ‘ majd kezd valamit.
Belsőleg a .NET UTF-16 kódolást alkalmaz, ami azt jelenti, hogy minden karakter két vagy 4 byte-on ábrázolódik belsőleg. Tehát például az A betű kódja hexadecimálisan a 0x4100 egy Low endian rendszeren. De mivel nem mindenki beszél UTF-16 kódolást, ezért a tényleges byte-ok sorozatára konvertálás szövegből egy absztrakciós rétegen keresztül történik.
Ezért az absztrakcióért az Encoding osztály felelÅ‘s. Ennek statikus adattagjaként elérhetÅ‘ek a legnépszerűbb kódolási módszerek, de semmi sem akadályoz meg bennünket, hogy készÃtsünk egy sajátot.
Saját karakterkódolás készÃtésének akkor van értelme, ha valami Å‘si, egzotikus 8 bites karakterlapot szeretnénk támogatni például a DOS idÅ‘kbÅ‘l. A .NET ezekhez beépÃtetten nem biztos, hogy tartalmaz Encoding implementációt, ezért elÅ‘fordulhat, hogy nekünk kell ezt leimplementálnunk. Egy másik használati eset lehet, ha mondjuk vonalkód generálás szövegbÅ‘l a feladatunk.
Használati esettÅ‘l függetlenül egyedi kódoláshoz implementálnunk kell az Encoding absztrakt osztály függvényeit és amikor szöveget Ãrunk TextWriter vagy olvasunk TextReadersegÃtségével, akkor átadjuk a használni kÃvánt példányunkat, és a konverziót az osztályok elvégzik belsÅ‘leg a szolgáltatott Encoding példány megfelelÅ‘ metódusainak a meghÃvásával.
Az osztály működését egy példán keresztül a legegyszerűbb elmagyarázni, ezért az EBDIC kódolást implementáljuk le. Az EBDIC a Extended Binary Coded Decimal Interchange Code rövidÃtése. Ezt a kódolást az IBM fejlesztette ki a System/360 számÃtógépeihez. A fejlesztése független volt a ASCII-tÅ‘l és semmilyen szinten nem kompatibilisek egymással. MeglepÅ‘ módon nem kihalt kódolásról beszélünk, mivel a mai napig számos IBM Mainframe támogatja a régi szoftverek futtathatóságának érdekében.
Az Encoding osztályból leszármazáshoz pár metódust kell felülÃrnunk. A GetByteCount metódus egy paraméterként adott karakter tömb részletérÅ‘l dönti el, hogy ténylegesen hány bájton ábrázolhatóak a karakterek. Ennek az inverze a GetCharCount a karakterek számát adja vissza egy byte tömbbÅ‘l. Mivel 8 bites kódolásról beszélünk, ezért a bájtok száma megegyezik a karakterek számával. Ezért mindkét metódusunknak csak a count paramétert kell visszaadnia.
Szintén hasonló implementáció kell a GetMaxByteCountés GetMaxCharCount metódusok implementációjához. Ezen metódusok az osztály belsÅ‘ memóriafoglalásában alkalmazottak. Ezek a metódusok váltakozó hosszúságú kódolás esetén a legrosszabb eset memóriahasználatát kell, hogy visszaadják. Esetünkben a legrosszabb eset megegyezik az átlag esettel, Ãgy mindkét esetben csak a paramétert adjuk vissza módosÃtás nélkül.
Az implementációs osztályunk és egy tesztelő program:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
internal sealed class EbdicEncoding : Encoding
{
private readonly Dictionary<byte, byte> _ebcdicToAscii;
private readonly Dictionary<byte, byte> _asciiToEbcdic;
public EbdicEncoding()
{
_asciiToEbcdic = new Dictionary<byte, byte>
{
{ 0x00, 0x00}, { 0x01, 0x01}, { 0x02, 0x02}, { 0x03, 0x03}, { 0x04, 0x37},
{ 0x05, 0x2D}, { 0x06, 0x2E}, { 0x07, 0x2F}, { 0x08, 0x16}, { 0x09, 0x05},
{ 0x0A, 0x25}, { 0x0B, 0x0B}, { 0x0C, 0x0C}, { 0x0D, 0x0D}, { 0x0E, 0x0E},
{ 0x0F, 0x0F}, { 0x10, 0x10}, { 0x11, 0x11}, { 0x12, 0x12}, { 0x13, 0x13},
{ 0x14, 0x3C}, { 0x15, 0x3D}, { 0x16, 0x32}, { 0x17, 0x26}, { 0x18, 0x18},
{ 0x19, 0x19}, { 0x1A, 0x3F}, { 0x1B, 0x27}, { 0x1C, 0x1C}, { 0x1D, 0x1D},
{ 0x1E, 0x1E}, { 0x1F, 0x1F}, { 0x20, 0x40}, { 0x21, 0x5A}, { 0x22, 0x7F},
{ 0x23, 0x7B}, { 0x24, 0x5B}, { 0x25, 0x6C}, { 0x26, 0x50}, { 0x27, 0x7D},
{ 0x28, 0x4D}, { 0x29, 0x5D}, { 0x2A, 0x5C}, { 0x2B, 0x4E}, { 0x2C, 0x6B},
{ 0x2D, 0x60}, { 0x2E, 0x4B}, { 0x2F, 0x61}, { 0x30, 0xF0}, { 0x31, 0xF1},
{ 0x32, 0xF2}, { 0x33, 0xF3}, { 0x34, 0xF4}, { 0x35, 0xF5}, { 0x36, 0xF6},
{ 0x37, 0xF7}, { 0x38, 0xF8}, { 0x39, 0xF9}, { 0x3A, 0x7A}, { 0x3B, 0x5E},
{ 0x3C, 0x4C}, { 0x3D, 0x7E}, { 0x3E, 0x6E}, { 0x3F, 0x6F}, { 0x40, 0x7C},
{ 0x41, 0xC1}, { 0x42, 0xC2}, { 0x43, 0xC3}, { 0x44, 0xC4}, { 0x45, 0xC5},
{ 0x46, 0xC6}, { 0x47, 0xC7}, { 0x48, 0xC8}, { 0x49, 0xC9}, { 0x4A, 0xD1},
{ 0x4B, 0xD2}, { 0x4C, 0xD3}, { 0x4D, 0xD4}, { 0x4E, 0xD5}, { 0x4F, 0xD6},
{ 0x50, 0xD7}, { 0x51, 0xD8}, { 0x52, 0xD9}, { 0x53, 0xE2}, { 0x54, 0xE3},
{ 0x55, 0xE4}, { 0x56, 0xE5}, { 0x57, 0xE6}, { 0x58, 0xE7}, { 0x59, 0xE8},
{ 0x5A, 0xE9}, { 0x5B, 0xBA}, { 0x5C, 0xE0}, { 0x5D, 0xBB}, { 0x5E, 0xB0},
{ 0x5F, 0x6D}, { 0x60, 0x79}, { 0x61, 0x81}, { 0x62, 0x82}, { 0x63, 0x83},
{ 0x64, 0x84}, { 0x65, 0x85}, { 0x66, 0x86}, { 0x67, 0x87}, { 0x68, 0x88},
{ 0x69, 0x89}, { 0x6A, 0x91}, { 0x6B, 0x92}, { 0x6C, 0x93}, { 0x6D, 0x94},
{ 0x6E, 0x95}, { 0x6F, 0x96}, { 0x70, 0x97}, { 0x71, 0x98}, { 0x72, 0x99},
{ 0x73, 0xA2}, { 0x74, 0xA3}, { 0x75, 0xA4}, { 0x76, 0xA5}, { 0x77, 0xA6},
{ 0x78, 0xA7}, { 0x79, 0xA8}, { 0x7A, 0xA9}, { 0x7B, 0xC0}, { 0x7C, 0x4F},
{ 0x7D, 0xD0}, { 0x7E, 0xA1}, { 0x7F, 0x07},
};
_ebcdicToAscii = _asciiToEbcdic.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
}
public override string EncodingName => "EBDIC";
public override bool IsSingleByte => true;
public override int GetByteCount(char[] chars, int index, int count)
=> count;
public override int GetCharCount(byte[] bytes, int index, int count)
=> count;
public override int GetMaxByteCount(int charCount)
=> charCount;
public override int GetMaxCharCount(int byteCount)
=> byteCount;
public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
{
for (int i = 0; i < charCount; i++)
{
char c = chars[charIndex + i];
byte ASCII = (byte)(c > 255 ? '?' : c);
bytes[byteIndex + i] = _asciiToEbcdic.TryGetValue(ASCII, out byte ebcdicValue)
? ebcdicValue
: _asciiToEbcdic[(byte)'?'];
}
return charCount;
}
public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
{
for (int i = 0; i < byteCount; i++)
{
byte EBDIC = bytes[byteIndex + i];
chars[charIndex + i] = _ebcdicToAscii.TryGetValue(EBDIC, out byte asciiValue)
? (char)asciiValue
: '?';
}
return byteCount;
}
}
internal class Program
{
private static void Main(string[] args)
{
Encoding ebdic = new EbdicEncoding();
string testString = "Hello, EBDIC!";
byte[] encoded = ebdic.GetBytes(testString);
Console.WriteLine($"Encoded bytes: {BitConverter.ToString(encoded)}");
string decoded = ebdic.GetString(encoded);
Console.WriteLine($"Decoded string: {decoded}");
}
}
A program kimenete:
Encoded bytes: C8-85-93-93-96-6B-40-C5-C2-C4-C9-C3-5A
Decoded string: Hello, EBDIC!
A konverziót a GetBytes és a GetChars végzi. Az elsÅ‘ UTF-16 karakterekbÅ‘l csinál byte sorozatot, mÃg a második egy bájt tömbbÅ‘l csinál karaktereket. EBDIC esetén a konverziós logikát a legegyszerűbben két konverziós táblával tudjuk megvalósÃtani. Mivel az EBDIC kódolás egy csomó karaktert nem támogat, ami az ASCII-ben is megtalálható, ezért a konverziós logikánk ezt ’?’ karakterekkel helyettesÃti, ami egy megszokott viselkedés .NET esetén.
Az EncodingName és IsSingleByte tulajdonságok virtuálisak, nem kötelező őket felüldefiniálni, de érdemes.