Vreemde tekens in console tonen

Niets is zo leuk als de vreemdste UNICODE tekens op het scherm tonen. In oude console-games werden deze tekens vaak gebruikt om complexe tekeningen op het scherm te tonen. Om je ietwat saaie applicaties dus wat toffer te maken, leg ik daarom uit hoe je dit kan doen.

Dit Wikipedia logo bestaat volledig uit UNICODE karakters.

UNICODE karakters tonen

Je toetsenbord heeft maar een beperkt aantal toetsen. Er zijn echter tal van andere tekens gedefinieerd die console-applicaties ook kunnen gebruiken. We zagen reeds dat al deze tekens, UNICODE-karakters, een eigen unieke code hebben die je kan opzoeken om vervolgens dat teken in je code te gebruiken.

Dit gaat als volgt in z’n werk:

  1. Zoek het teken(s) dat je nodig hebt in een UNICODE-tabel1 en noteer de hexadecimale waarde.
  2. Zet als allereerste lijn in je Main (dus nog vóór je iets met Console.Write of WriteLine naar het scherm stuurt): Console.OutputEncoding = System.Text.Encoding.UTF8;. Zet je deze lijn pas later, dan kan tekst die je daarvoor al toonde verkeerd verschijnen.
  3. Je kan nu op 2 manieren dit teken op het scherm krijgen.

Stel je voor dat we het copyright karakter wensen te gebruiken (de letter c in een cirkeltje) in onze applicatie. Deze heeft hexadecimale UNICODE waarde 0x00A9.

Die 0x vooraan zag je al bij de literals: het getal erna is hexadecimaal geschreven. Je telt dan met zestien cijfers, 0 tot 9 en daarna A tot F. UNICODE-tabellen geven hun codes altijd zo. 0x00A9 is gewoon het getal 169 (A staat voor 10, en 10 × 16 + 9 = 169). Ook de letter 'A' uit de vorige sectie heeft zo’n code: 0x0041, oftewel 65.

Manier 1: copy/paste

Kopieer het karakter zelf en plaats het in je code waar je het nodig hebt, bijvoorbeeld:

Console.WriteLine("<plak hier je speciale teken>"); 

Merk op dat niet elk lettertype elk teken kent. Kent het lettertype van je consolevenster een teken niet, dan zie je een vierkantje. In Windows Terminal (de standaard op Windows 11) is dat zelden een probleem, zelfs voor emoji. In het oudere zwarte consolevenster en in de Debug Console van Visual Studio gebeurt het wel nog.

Een vraagteken in plaats van je teken is iets anders. Dat ligt nooit aan het lettertype, maar aan een ontbrekende UTF-8-lijn uit stap 2, ook in Windows Terminal. Waarom, lees je verderop in Waarom soms een vraagteken?.

Manier 2: hexadecimale code casten naar char

Casting leg ik pas in het volgende hoofdstuk uit, maar het kan geen kwaad om al eens een voorproefje hiervan te krijgen.

Noteer de hexadecimale code van het karakter dat in de tabel staat. In dit geval is dat dus 0x00A9. Om dit teken te tonen schrijf je dan:

char copyright = (char)0x00A9;
Console.WriteLine(copyright);

Dit kan ook korter. Door gebruik te maken van de \u-notatie om hexadecimale waarden voor te stellen:

Console.WriteLine("\u00A9");

Het teken opzoeken in de UNICODE-tabel (00A9) en het op drie manieren in je code zetten. Alle drie tonen hetzelfde © op het scherm.

Opmerking

En emoji? De \u-notatie verwacht exact vier hexadecimale cijfers, en een char kan enkel tekens tot en met 0xFFFF bevatten. Emoji zitten daarboven: een lachend gezichtje is bijvoorbeeld 0x1F600, en (char)0x1F600 compileert dan ook niet. Wil je toch een emoji tonen, dan gebruik je de \U-notatie (hoofdletter U) met acht cijfers, of je plakt de emoji gewoon in je string zoals bij manier 1:

Console.WriteLine("\U0001F600");

Kennisclip: Speciale tekens op je scherm: ©, ╔ en 😀

Waarom soms een vraagteken?

Waarom moet die UTF-8-lijn er eigenlijk bij? En waarom zie je soms een ?, soms een vierkantje en soms iets als €? Volg even de reis van één teken, het euroteken, van je code tot op je scherm.

De reis van een teken van je code tot op het scherm. Op drie plaatsen kan het mislopen, elk met een eigen symptoom.

De reis van een teken van je code tot op het scherm. Op drie plaatsen kan het mislopen, elk met een eigen symptoom.
  1. Je code. In je string staat "€".
  2. UNICODE. In het geheugen zit niet het teken zelf, maar zijn nummer: 0x20AC, oftewel 8364. Zolang de tekst binnen je programma blijft, kan er niets mislopen: in UTF-16 past elk UNICODE-teken.
  3. Encoding. Console.WriteLine stuurt geen tekens naar het consolevenster, maar bytes. Welke bytes, dat bepaalt Console.OutputEncoding. In UTF-8 wordt de € drie bytes: E2 82 AC. Zonder de UTF-8-lijn gebruikt een Belgische Windows-pc code page 850, een tabel van 256 tekens uit de tijd van MS-DOS. Het euroteken bestond toen nog niet en staat er dus niet in. C# kan het dan niet omzetten en zet er zelf een ? in de plaats.
  4. Venster. Het consolevenster leest de bytes, zoekt op welk teken erbij hoort en tekent dat met zijn lettertype. Heeft het lettertype geen tekening voor dat teken, dan zie je een vierkantje.

Het symptoom vertelt je dus waar het misliep:

Je ziet Waar liep het mis?
? Bij de encoding: het teken staat niet in de tabel. Zet de UTF-8-lijn bovenaan.
een vierkantje Bij het venster: het lettertype heeft geen tekening voor dit teken. Aan je code ligt het niet.
€ of é Tussen encoding en venster: de bytes worden gelezen met een andere tabel dan waarmee ze geschreven werden.

Dat laatste gebeurt zelden in een consolevenster, want Console.OutputEncoding stelt het venster meteen op dezelfde tabel in. Bij bestanden en mails loopt het wel geregeld mis, zie het kader hieronder.

Je ziet het verschil het best met één regel:

Console.WriteLine("A é € ╔ 😀");

Zonder de UTF-8-lijn geeft dit op een Belgische pc:

A é ? ╔ ??

De é en de ╔ staan wel in code page 850 en komen er dus gewoon door. De emoji wordt twee vraagtekens: C# bewaart ze als twee chars (het surrogate pair uit het begin van dit hoofdstuk), en elk stuk wordt apart een ?. Met de UTF-8-lijn bovenaan komt alles door:

A é € ╔ 😀

In het oudere consolevenster wordt de emoji ook dan nog een vierkantje: dat venster kan het teken niet tekenen.

Hetzelfde geldt voor het geldbedrag uit de vorige sectie: bij {12.3456:C} verscheen het euroteken als een ?. Met de UTF-8-lijn ervoor staat er een €.

Zie je bij jou een euroteken, ook zonder die lijn? Op een Mac en onder Linux gebruikt de console altijd UTF-8. Op Windows kan je UTF-8 als standaard instellen (een optie onder de taalinstellingen). Bij je medestudenten is dat mogelijk niet zo, dus zet de lijn er toch altijd bij.

Kennisclip: Waarom zie ik een vraagteken? De reis van een teken

Ooit een mail gekregen waarin Café als Café stond, of een prijs met € in plaats van een euroteken? Dan heb je mojibake gezien, Japans voor “tekens die van gedaante veranderd zijn”.

Het gebeurt telkens een programma tekst leest met een andere tabel dan waarmee ze geschreven werd. In UTF-8 bestaat de é uit twee bytes: C3 en A9. Een programma dat denkt dat het met een oude tabel van 256 tekens te maken heeft (zoals Windows-1252, de tabel van oudere Windows-programma’s), maakt daar twee tekens van: à en ©. Het euroteken is in UTF-8 zelfs drie bytes, en zo wordt het €.

UNICODE-kunst tonen

Soms zou je multiline UNICODE-kunst (ook wel ASCII-art genoemd) willen tonen in je C# applicatie. Dit kan je eenvoudig oplossen door gebruik te maken van het @ teken voor een string.

Stel dat je een toffe titel of tekening bijvoorbeeld via ASCIIflow.com maakt. Je kan het resultaat eenvoudig naar je klembord kopiëren en vervolgens in je C#-code integraal copy pasten als literal voor een string op voorwaarde dat je het laat voorafgaan door @" en uiteraard eindigt met ";.

Bijvoorbeeld:

string myname = @"
___________________   
\__    ___/\______ \  
  |    |    |    |  \ 
  |    |    |    `   \
  |____|   /_______  /
                   \/ ";
Console.WriteLine(myname);
Tip

Zowel de $-notatie (voor string interpolatie) als het @-teken kan je gecombineerd gebruiken bij een string. De volgorde maakt niet uit: zowel $@"..." als @$"..." is toegelaten en doet exact hetzelfde.

Console.WriteLine($@"1+1={1+1}. \tGeen tab");

Dit geeft als output (ordt door het apenstaartje genegeerd):

1+1=2. \tGeen tab

Raw string literals

Het @-teken heeft één nadeel: alles tussen de aanhalingstekens hoort bij de string, ook de spaties waarmee je je code inspringt. Je tekening moet dus tegen de linkerkantlijn plakken, anders krijg je die inspringing mee op het scherm. Sinds C# 11 is er een modernere manier: de raw string literal. Je begint en eindigt de string met drie aanhalingstekens, elk op een eigen lijn:

string myname = """
    ___________________   
    \__    ___/\______ \  
      |    |    |    |  \ 
      |    |    |    `   \
      |____|   /_______  /
                       \/ 
    """;
Console.WriteLine(myname);

De positie van de sluitende """ bepaalt wat er weggeknipt wordt: alle lijnen ertussen moeten minstens even ver ingesprongen zijn als die sluitende """, en precies die inspringing verdwijnt uit het resultaat. Backslashes, aanhalingstekens en escape-codes zoals \t worden net als bij @ letterlijk genomen. Ook hier kan je een $ voor zetten om interpolatie te gebruiken: $"""...""".

Je zal @"..." nog vaak tegenkomen in bestaande code en in code die AI-tools genereren. Beide manieren werken.

Kennisclip: Tekst letterlijk nemen: @ en raw strings

Het ultieme spel gemaakt uit niets dan tekens is nog altijd Dwarf Fortress. Tarn Adams begon eraan in 2002, het jaar van GTA Vice City en Morrowind, en bracht de eerste publieke versie uit in 2006. Terwijl de rest van de wereld in 3D speelde, bouwde je daar een dwergenkolonie in een raster van letters.

En dat ding zat fenomenaal diep. Elke dwerg heeft een eigen humeur, een eigen geschiedenis en eigen voorkeuren, de wereld wordt laag per laag gesimuleerd en zelfs het water zoekt zijn weg door je gangen. Brakke graphics, en toch bleven mensen er jaren in hangen. Het bewijst nog maar eens dat een sexy frontend minder waard is dan een backend die gewoon werkt. En dat laatste is net waar jij nu voor aan het studeren bent.


  1. Zie symbl.cc (vroeger unicode-table.com).↩︎