Wat leer je in dit hoofdstuk?

Na dit hoofdstuk kan je:

data omzetten via casting, parsing en de Convert-bibliotheek

het verschil tussen narrowing en widening uitleggen

gebruikersinvoer correct inlezen en omzetten naar een getal

rekenen met System.Math en methoden in elkaar schuiven

bewust afronden (let op bankers rounding) en weten wanneer je decimal nodig hebt

willekeurige getallen genereren met Random

debuggen met breakpoints en je code stap voor stap doorlopen

Appelen en peren

Een waarde van het ene type zomaar aan een ander type toekennen mag niet:

int leeftijd = 4.3;  // FOUT: een double past niet zomaar in een int

Je kan geen appelen in peren veranderen zonder magie. In C# heb je drie manieren:

Casting

de klassieke manier (ook in C, C++, Java)

Convert-bibliotheek

van .NET

Parsing

enkel om een string om te zetten

Casting

Plaats het doeltype tussen haakjes voor de waarde:

double kommagetal = 13.8;
int afgekapt = (int)kommagetal;  // 13

Belangrijk

Een variabele verandert nooit van type. Je zet enkel de waarde om en gebruikt die elders. kommagetal blijft een double.

Narrowing

Narrowing = een type omzetten met verlies aan data (bv. double naar int).

Casting naar int gooit alles na de komma weg.

Dit vereist een expliciete cast

Je zegt zo tegen de compiler: “ik weet dat er data verloren gaat, en ik draag de verantwoordelijkheid”

Casten rondt niet af

(int)20.9 geeft 20

De klassieke valkuil

tempGisteren en tempVandaag zijn int (20 en 25):

double gem = (tempGisteren + tempVandaag) / 2;

Dit geeft 22.0, niet 22.5. De expressie bevat enkel int’s, dus de deling is een gehele deling. Casten lost het op:

double gem = ((double)tempGisteren + tempVandaag) / 2;

Links blijft elk tussenresultaat een int, rechts rekent de hele expressie als double.

Belangrijk

De plaats van je cast telt: (double)(a + b) / 2 geeft 22.5, maar (double)((a + b) / 2) geeft 22. Daar dwingen de haakjes de gehele deling eerst af.

Widening

Een klein type in een groter type past zonder dataverlies, dus geen cast nodig:

int hoofdMeting = 20;
double secundaire = hoofdMeting;  // 20.0

Casten mag hier wel ((double)hoofdMeting), maar het hoeft niet.

Widening past zonder verlies, narrowing loopt over tenzij je met een cast tekent voor dat verlies.

Mars Climate Orbiter

1999: veel te laag de atmosfeer in

Een ruimtesonde van

125

miljoen dollar, verloren door een getal zonder eenheid.

Pound-force of newton?

Allebei gewoon een getal, allebei perfect gecompileerd, geen enkele foutmelding.

Wat een datatype niet weet

Wat het getal betekent, moeten mensen onder elkaar afspreken.

Parsing en .ToString()

Parsing

zet een string om naar een ander type

int numVal = int.Parse("-105");

.ToString()

de omgekeerde weg, naar tekst (dat elk type heeft)

int getal = 5;
string tekst = getal.ToString();

Tip

Niet zeker dat de gebruiker een geldig getal invoert? Dan is int.TryParse() je vriend (zie appendix).

Invoer van de gebruiker verwerken

ReadLine geeft altijd een string

De gebruiker typt 3 en daarna 4. Wat toont dit?

string a = Console.ReadLine();
string b = Console.ReadLine();
Console.WriteLine(a + b);
3 [enter]
4 [enter]
34

Zelfde invoer, ander resultaat.

Geen crash: de + plakt twee strings aan elkaar. C# heeft geen idee dat jij met "3" en "4" wou rekenen.

Waarschuwing

int getal = Console.ReadLine(); mag evenmin: de compiler weigert een string in een int te steken.

Van string naar getal

Uitlezen

met ReadLine

Bewaren

in een string

Parsen

naar het type dat je nodig hebt

Console.WriteLine("Geef je gewicht:");
double gewicht = double.Parse(Console.ReadLine());

Wat de gebruiker typt komt binnen als string, Parse maakt er een double van.

De regel die je honderden keren typt

int getal = int.Parse(Console.ReadLine());

Enkel de Parse-methode verandert mee met het type dat je nodig hebt:

Gewenst type Code
int int.Parse(Console.ReadLine())
double double.Parse(Console.ReadLine())
bool bool.Parse(Console.ReadLine())
char char.Parse(Console.ReadLine())
string Console.ReadLine() (geen Parse nodig)

Opmerking

De komende hoofdstukken mag je ervan uitgaan dat de gebruiker foutloze invoer geeft.

Stagiair Steven

Steven moet de leeftijd van de gebruiker inlezen. De A.I. gaf hem dit en hij noemt het zelf “robuust”:

Console.WriteLine("Geef je leeftijd:");
int leeftijd = int.Parse(Console.ReadLine());
Console.WriteLine($"Volgend jaar word je {leeftijd + 1}.");
Geef je leeftijd:
25 [enter]
Volgend jaar word je 26.

“Werkt perfect, ik heb het getest met 25”, zegt hij trots. Wat als de gebruiker vijfentwintig typt, of gewoon op enter duwt?

Stagiair Steven: wat liep er mis?

Console.WriteLine("Geef je leeftijd:");
int leeftijd = int.Parse(Console.ReadLine());
Console.WriteLine($"Volgend jaar word je {leeftijd + 1}.");
Geef je leeftijd:
vijfentwintig [enter]
Unhandled exception. System.FormatException: The input string 'vijfentwintig' was not in a correct format.

Opmerking

Dan crasht zijn programma. int.Parse verwacht tekst die echt een geheel getal voorstelt. Steven testte enkel de invoer die hij zelf in gedachten had, en de A.I. waarschuwde hem nergens. Foute invoer opvangen doe je met int.TryParse.

Komma of punt?

In je code

schrijf je een kommagetal-literal altijd met een punt: 9.81 (onafhankelijk van je taalinstellingen)

Als invoer

hangt het van de landinstellingen af: een Belgisch systeem verwacht 9,81, een Engels 9.81

Waarschuwing

Het verkeerde scheidingsteken geeft geen foutmelding. Op een Belgische computer wordt 9.81 gelezen als 981, want de punt geldt daar als scheiding voor duizendtallen. Je programma rekent vrolijk verder met een gewicht van 981 kilogram.

Vreemde resultaten bij kommagetallen zonder dat er iets crasht? Kijk eerst welk teken je hebt getypt.

De Convert-bibliotheek

int g = Convert.ToInt32(3.2);
double d = Convert.ToDouble(5);
bool b = Convert.ToBoolean(1);
int l = Convert.ToInt32("19");

Convert is een zwarte doos.

Naar int is .ToInt32(), naar short is .ToInt16()

Een zwarte doos: je ziet niet of er gecast of geparset wordt

Tip

Convert.ToBoolean geeft voor elk getal True, behalve voor 0 (of 0.0). En een bool naar een int omzetten lukt enkel via Convert, niet via casting.

Casting, conversie of parsing?

Alle omzettingen samengevat.

Casting en parsing zijn compact en werken ook in andere talen. Beheers ze dus, ook al is Convert verleidelijk eenvoudig.

Berekeningen met System.Math

De Math-bibliotheek

double macht = Math.Pow(getal, 3);   // grondtal, exponent
double wortel = Math.Sqrt(144);      // 12
double omtrek = Math.PI * 2 * straal;

Verder o.a. Sin Cos Tan Ceiling Floor Abs Max Min Clamp

Voor Math schrijf je nooit new.

Waarschuwing

Sin, Cos en Tan rekenen in radialen. Math.Sin(90) geeft 0.8939... en niet 1. Reken eerst om: double hoek = graden * Math.PI / 180;

Tip

Typ Math. en IntelliSense toont alles. Cursor op een methode + F1 opent de documentatie.

Math geeft kommagetallen terug

int resultaat = Math.Pow(2, 10);  // compileert niet!

Cannot implicitly convert type ‘double’ to ‘int’. Bijna alle rekenmethoden van Math geven een double terug, ook al stop je er gehele getallen in. Je lost het op zoals daarnet:

double resultaat = Math.Pow(2, 10);          // 1024
int resultaatAlsInt = (int)Math.Pow(2, 10);  // 1024

Tip

Uitzonderingen zijn Abs, Max, Min en Clamp: die geven terug wat je erin stopt. Math.Abs(-42) is dus een int.

Methoden in elkaar schuiven

Het resultaat van een methode mag de parameter van een andere methode zijn. Dat deed je al in double.Parse(Console.ReadLine()). De afstand tussen speler en monster, met Pythagoras:

double afstand = Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2));

C# werkt van binnen naar buiten: eerst de twee machten, dan de optelling, en pas dat totaal gaat in Sqrt.

Waarden begrenzen

levens = Math.Max(levens - schade, 0);        // nooit onder 0
volume = Math.Clamp(volume, 0, 100);          // 130 wordt 100
int verschil = Math.Abs(vandaag - gisteren);  // 5, niet -5

Max / Min

de grootste of de kleinste van twee getallen

Clamp

begrenzen langs twee kanten tegelijk (waarde, ondergrens, bovengrens)

Abs

het minteken verdwijnt, ideaal voor “hoe groot is het verschil?”

Tip

Deze heb je in de praktijk vaker nodig dan alle sinussen samen.

Als rekenen misloopt

Console.WriteLine(a / b);          // int door 0: DivideByZeroException
Console.WriteLine(x / y);          // double door 0.0: oneindig
Console.WriteLine(Math.Sqrt(-1));  // NaN, Not a Number

NaN is besmettelijk: NaN + 5 blijft NaN, NaN * 1000 ook.

Controleren doe je met double.IsNaN(resultaat), dat geeft je een bool.

Belangrijk

Oneindig en NaN laten je programma niet crashen. De foute berekening werkt stilletjes door, soms tot honderden regels verder waar je plots een onmogelijk resultaat ziet.

Het bereik in code

Elk datatype kent z’n eigen grenzen, en de compiler kent ze ook:

Console.WriteLine($"int gaat van {int.MinValue} tot {int.MaxValue}");
int gaat van -2147483648 tot 2147483647

Bij kommagetallen bestaat zelfs double.PositiveInfinity en double.NegativeInfinity.

int groot = int.MaxValue;
Console.WriteLine(groot + 1);  // -2147483648
-2147483648

Waarschuwing

Overflow: de teller loopt over zoals de kilometerteller van een oude auto. Geen crash, geen waarschuwing. Verwacht je grote getallen, kies dan meteen een long.

Over afronden

Afkappen is niet afronden

Casting kapt af, ongeacht of er een 1 of een 9 na de komma staat. Bij negatieve getallen zie je het verschil met echt afronden:

Console.WriteLine((int)-2.7);        // -2, richting nul
Console.WriteLine(Math.Floor(-2.7)); // -3, richting min oneindig

Math.Truncate doet exact wat een cast doet, maar geeft je een double terug.

Floor en Ceiling gaan altijd dezelfde kant uit, een cast schuift altijd naar de nul toe.

Hoeveel bussen heb je nodig?

47 studenten, 20 per bus:

int aantalBussen = (int)Math.Ceiling(aantalStudenten / (double)perBus); // 3

47 / 20.0

2.35

Math.Ceiling

3.0

(int)

3

Overal waar je iets moet vullen (bussen, dozen, pagina’s) mag je niets laten liggen, en daar is Math.Ceiling voor.

Waarschuwing

Vergeet je de cast naar double, dan deel je twee gehele getallen en krijg je 2 nog voor Ceiling iets kan doen. Er staan dan 7 studenten op de parking.

Bankers rounding

Math.Round(4.5);  // 4
Math.Round(5.5);  // 6

Math.Round rondt standaard af naar het dichtstbijzijnde even getal.

Rond je de reeks 0.5 tot 5.5 allemaal naar boven af, dan tel je er 3 bij. Met bankers rounding kom je exact op de echte som uit.

Dezelfde reeks op twee manieren afgerond.

Waarschuwing

Handig voor bankiers, minder handig wanneer jouw quotering van 4.5 op 10 plots een 4 wordt.

Afronden zoals je verwacht

Geef een MidpointRounding mee:

Math.Round(4.5, MidpointRounding.AwayFromZero);         // 5
Math.Round(4.12343, 2, MidpointRounding.AwayFromZero);  // 4,12

De volgorde van de parameters ligt vast:

eerst het getal

dan het aantal cijfers na de komma

pas als laatste de MidpointRounding

Belangrijk

Convert.ToInt32 doet ook aan bankers rounding, en daar kan je het niet afzetten. Rond dan eerst zelf af: (int)Math.Round(4.5, MidpointRounding.AwayFromZero).

De drie manieren naast elkaar

Waarde (int) Math.Round met AwayFromZero Convert.ToInt32
4.5 4 4 5 4
5.5 5 6 6 6
-2.5 -2 -2 -3 -2

Belangrijk

Kies bewust. Wil je afkappen, gebruik een cast of Math.Truncate. Wil je afronden, gebruik Math.Round met MidpointRounding.AwayFromZero. Doe je niets, dan krijg je bankers rounding.

Afronden of enkel mooi tonen?

double gemiddelde = 7.0 / 3.0;

Console.WriteLine(gemiddelde);          // 2,3333333333333335
Console.WriteLine($"{gemiddelde:F2}");  // 2,33

Bij F2

behoudt de variabele al haar cijfers, enkel de tekst op het scherm is ingekort

Bij Math.Round

gooi je die cijfers echt weg

Tip

Rond pas af op het laatste moment, vlak voor je het resultaat toont. Rond je te vroeg af, dan sleep je die fout doorheen je hele berekening mee.

Geld: reken niet met double

Console.WriteLine(0.1 + 0.2);     // 0,30000000000000004

double prijs = 4.35;
int centen = (int)(prijs * 100);  // 434 in plaats van 435!

4.35 * 100

levert 434.99999999999994

(int)

de cast kapt de rest af

434

in plaats van 435

Je klant is een cent kwijt, en bij duizend bestellingen merkt de boekhouding dat.

decimal prijs = 4.35M;   // decimal bewaart de cijfers decimaal

Tip

Voor alles wat op een kassaticket zou staan kies je decimal. Voor wetenschappelijke berekeningen, coördinaten en snelheden blijft double de juiste keuze.

Random getallen genereren

De Random-generator

Maak eenmalig een generator en vraag dan getallen op:

Random generator = new Random();
int getal1 = generator.Next();
int a = generator.Next(0, 11);    // 0 tot en met 10
int b = generator.Next(55, 100);  // 55 tot en met 99

Belangrijk

De ondergrens telt mee, de bovengrens niet. Wil je een getal tot en met X, schrijf dan X + 1 als tweede parameter.

Tip

De new Random()-syntax pluizen we vanaf hoofdstuk 9 uit. Lig er nu nog niet van wakker.

Kommagetallen met NextDouble

NextDouble() geeft een getal tussen 0.0 en 1.0. Schaal en verschuif naar je bereik:

// tussen 5.0 en 12.5
double r = 5.0 + (generator.NextDouble() * 7.5);

Bereik = 12.5 - 5.0 = 7.5, daarna +5 om te verschuiven.

Bereik van Random verschuiven en schalen.

Steeds dezelfde getallen?

Een dobbelsteen gooide

10

keer na elkaar hetzelfde getal.

Dezelfde code, twee versies van .NET

De klok als seed

Het oude .NET Framework

In het huidige .NET krijgt elke Random een eigen, willekeurige seed.

Een vaste seed

Maak toch maar één generator aan. Telkens dezelfde reeks nodig, bv. om je code te testen? Geef dan zelf een seed mee:

Random generator = new Random(666);
Console.WriteLine(generator.Next());

Tip

Het hoeft geen 666 te zijn: elk getal is een andere seed.

Seeds in Balatro en Minecraft

Met dit ene getal

666

ligt je hele wereld vast.

Zelfde seed, zelfde reeks

Jouw wereld op een andere pc

Er bestaan hele forums vol goeie seeds.

Debuggen

Logica vs syntax

Code die compileert, is enkel grammaticaal correct.

De betekenis kan nog altijd fout zijn:

Neem natrium
Neem water
Voeg beide samen

Perfecte “code”, maar een logische fout (en een stevige explosie). Dat is een bug.

Een bug in Visual Studio?

“Meneer, ik heb een bug in Visual Studio gevonden.”

99%

van de keren zit die bug in je eigen code.

Breakpoints

Klik links van een lijn voor een breakpoint (rode bol)

Je programma pauzeert daar

In Locals/Autos zie je de waarde van elke variabele

Je kan ook over een variabele hoveren.

Een breakpoint.

Door je code steppen

Continue

verder tot het volgende breakpoint of het einde

Step over (het gebogen pijltje)

één lijn verder, dan weer pauzeren

Stop (rode knop)

stoppen om je code aan te passen

Voorspel telkens eerst wat er moet gebeuren: welke waarden, welke output?

Klopt de werkelijkheid niet met je voorspelling, dan heb je waarschijnlijk een bug beet.

Waarschuwing

Zomaar door je code steppen en hopen dat de bug magisch verschijnt, werkt niet. Een programmeur die niet kan debuggen is als een vis die niet kan zwemmen.

Programmeren met A.I.

Belangrijk

De verboden prompt: “Schrijf de C#-oplossing voor deze opgave.” Dat is als de Tour de France-winnaar de Mont Ventoux voor je laten opfietsen: je geraakt boven, maar leert niets.

Beter zijn prompts die je doen denken en waarmee je in dialoog gaat:

Zoek de fout

laat AI code met ingebouwde fouten maken

Vergelijken

je oplossing met de modeloplossing

Ondervragen

laat je ondervragen over de output van een stukje code

Links doet de A.I. het denkwerk, rechts doe jij het.

Conclusie

Omzetten kan via casting, parsing en Convert: ken het verschil

Narrowing (met dataverlies) vereist een cast, widening gaat vanzelf

ReadLine geeft altijd een string: parse naar het juiste type

Math rekent met kommagetallen en in radialen; oneindig en NaN crashen niet

Kies bewust tussen afkappen en afronden; voor geld gebruik je decimal

Eén Random-generator volstaat; debuggen doe je door te voorspellen

Tip

Volgende halte: beslissingen nemen met if, vergelijkingen en switch.

Meer weten

Kennisclips

Oefeningen H4 · Quizlet flashcards

Zie verder

Hoe doen andere programmeertalen hetzelfde? En een korte zoek-de-fout, om zo kritisch mogelijk naar code te leren kijken.

Zie verder: type-coercion

C# is streng. JavaScript zet types net automatisch om (coercion):

console.log("5" + 3);  // "53"  (de 3 wordt een string)
console.log("5" * 3);  // 15    (de "5" wordt een getal)
53
15

Die eerste regel doet C# ook: "5" + 3 levert daar eveneens de tekst "53" op. Het verschil zit in de tweede regel: "5" * 3 weigert de C#-compiler.

Zie verder: omzetten in Python

De naam van het type is er meteen de omzetfunctie:

gewicht = float(input("Geef je gewicht: "))

Wiskunde zit in een aparte module die je eerst moet importeren:

import math

wortel = math.sqrt(144)   # 12.0

C# heeft een aparte Parse per type, en Math hoort standaard bij de taal.

Zie verder: zoek de fout

Deze Python-code rondt twee examencijfers af. Eén van de twee studenten gaat klagen. Waarom?

cijfer1 = 4.5
cijfer2 = 5.5

print(round(cijfer1))  # 4
print(round(cijfer2))  # 6

round() doet in Python ook aan bankers rounding: ligt een getal op de helft, dan gaat het naar het dichtstbijzijnde even getal. Wat Python niet heeft is de MidpointRounding-parameter.

Samenvattende poster

Een handige samenvattende poster (Opgelet: gemaakt m.b.v. ChatGPT Image Gen 2).