Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 165 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

1.0.0

Loading...

Inleiding

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Semester 1: Programming Principles

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

H2: Variabelen en datatypes

Loading...

Loading...

Loading...

Loading...

Loading...

H3: Strings en hun methoden

Loading...

Loading...

Loading...

Loading...

Loading...

H4: Beslissingen

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

H5: Loops

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

H7: Methoden

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

H10: Gevorderde tekstverwerking

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Semester 2 : OOP

H10: Klassen en objecten

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

H11: Objecten (al dan niet) aanmaken

Loading...

Loading...

Loading...

H12: Geheugenmanagement bij klassen

Loading...

Loading...

Loading...

Loading...

H13: Datastructuren

Loading...

Loading...

Loading...

Loading...

Loading...

Afspraken oefeningen

Voor de graduaatsopleiding hanteren we een vaste structuur voor de oefeningen.

vorm van de oefeningen

unieke titel

Elke oefening begint met een unieke titel. Deze gebruik je voor de organisatie van je bestanden.

korte toelichting

Eerst krijg je een omschrijving in tekst van wat de algemene bedoeling is van je programma. Deze bevat niet noodzakelijk elk detail, maar geeft je een big picture. Bijvoorbeeld:

In deze oefening maak je een echo. Dit is een programma dat tekst inleest vanaf het toetsenbord en deze tekst dan op het scherm toont.

vertrekpunt

Je hoeft niet altijd alle code zelf te schrijven. Code die je nog niet verondersteld wordt zelf te kunnen schrijven, of code die gewoon veel tijd in beslag neemt, krijg je. Bij de stukken code staat telkens de naam van het bestand waarin ze thuishoort. Commentaar bij de te wijzigen stukken code geeft aan welke aanpassingen je moet doen. Bijvoorbeeld:

Program.cs

using System;

namespace Programmeren {
    class Program {
        public static void Main(string[] args) {
            // zorg er zelf voor dat een regel tekst die gebruiker intypt weer verschijnt
            string message;
            Console.WriteLine(message);
        }
    }
}

voorbeeldinteractie

Ten slotte worden één of meerdere voorbeelden getoond van hoe je programma zich gedraagt. In deze voorbeelden stellen regels die niet beginnen met > iets voor dat op het scherm verschijnt. Regels die wel beginnen met > stellen iets voor dat door de gebruiker zelf wordt ingetypt. Bijvoorbeeld, voor het programma dat we hier als leidraad gebruiken:

> Hallo, wereld!
Hallo, wereld!

We gebruiken de speciale syntax <EOF> om aan te geven dat er een end-of-file signaal wordt gestuurd.

Benodigdheden

In alle lessen (hoorcollege en practica) hebben we 2 zaken nodig:

  1. Deze cursus

  2. Een laptop met daarop Visual Studio Code editie geïnstalleerd.

Welkom

Indien bepaalde hoofdstukken of onderdelen niet duidelijk zijn of je hebt suggesties ter verbetering/aanvulling, aarzel dan niet om ons te contacteren.

Veel leer- en leesplezier...

Dankwoord

Deze cursus wordt gebruikt als handboek binnen de opleiding .

Deze cursus is een fork van . We zijn Tim Dams en al zijn co-auteurs dank verschuldigd voor de stevige basis. Je mag veronderstellen dat alles Tims werk is, wij hebben vooral de vorm van de oefeningen aangepast voor een meer beroepsgerichte opleiding.

graduaat programmeren van de AP Hogeschool
die van de richting toegepaste informatica

H1: Werken met Visual Studio

Visual Studio en .NET Core installeren

We hebben twee zaken nodig om C# programma's te schrijven.

Met een editor, zoals Visual Studio, krijgen we ondersteuning bij het schrijven van de code die we later zullen uitvoeren. Een editor vergemakkelijkt dus het schrijven van een specifiek soort tekst.

De "SDK" (software development toolkit) bevat alles wat je nodig hebt om de code daadwerkelijk uitvoerbaar te maken.

Vooraleer je begint

  1. Voor deze opdrachten gebruiken we .NET Core 6.0, maar we gebruiken de (explicietere) template van .NET Core 5.0. Je moet dus beide SDK's installeren.

  2. Sommige afbeeldingen hieronder dateren van de tijd van .NET Core 2.0 en Visual Studio 2017. Soms kunnen zaken er op jouw scherm dus wat anders uitzien dan in deze cursus.

Installatie

Visual Studio 2022 Community

    • In het tabblad individual components klik je .NET 5.0 Runtime (out of support) nog extra aan.

  1. Vervolgens kies je rechts onderaan voor de knop Install

Flowgorithm

Eerste gebruik Visual Studio

  1. Kies voor Create New Project

  2. Vervolgens selecteer je Console App

  3. Je geeft je project een goede naam, hieronder dien je de Project name dan aan te passen en je zal zien dat de Solution name automatisch mee wordt gewijzigd. Denk er ook aan om je project op de juiste plaats op te slaan door de Location te wijzigen.

  4. In de volgende stap dien je als Framework .NET 6.0 te kiezen en de optie "Do not use top-level statements" aan te vinken.

Nadat je je project hebt aangemaakt, rechterklik je op je project in de Solution Explorer -> Properties -> Build -> General en schakel je "Implicit global usings" uit.

Code Snippet

Hierbij ga je ervoor zorgen dat je met afkortingen kan coderen die dan automatisch in het goede formaat worden omgezet.

Bv. cr (gevolgd door twee TABs) wordt dan Console.ReadLine()

Je kan volgende code kopiëren in een tekstbestand met als extensie .snippet of je kan het hieronder downloaden.

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
 <CodeSnippet Format="1.0.0">
 <Header>
 <Title>cr</Title>
 <Shortcut>cr</Shortcut>
 <Description>Code snippet for Console.ReadLine</Description>
 <Author>Community</Author>
 <SnippetTypes>
 <SnippetType>Expansion</SnippetType>
 </SnippetTypes>
 </Header>
 <Snippet>
 <Declarations>
 <Literal Editable="false">
 <ID>SystemConsole</ID>
 <Function>SimpleTypeName(global::System.Console)</Function>
 </Literal>
 </Declarations>
 <Code Language="csharp">
 <![CDATA[$SystemConsole$.ReadLine();$end$]]>
 </Code>
 </Snippet>
 </CodeSnippet>
</CodeSnippets>

Vervolgens dien je binnen Visual Studio in het menu Tools naar Code Snippets Manager gaan.

Je kiest voor Language CSharp en klikt op de knop Import.

Je kiest het snippet bestand en voegt dit toe.

Kies voor de "community" versie

Het installatiescherm ziet er als volgt uit, in de volgende punten wordt beschreven welke elementen je specifiek moet aanduiden.

Kies bij de installatie voor .NET desktop development

Bij de installation details kies je voor volgende elementen

Log in met jouw account van de hogeschool, nl. s-nummer@ap.be

Opmerking .NET 5.0 Runtime kan je ook afzonderlijk installeren via

Flowgorithm is een visuele programmeertaal. We zullen deze gebruiken om bepaalde concepten duidelijk zichtbaar te maken. Je moet de Windows Installer van halen. Tijdens de installatie klik je gewoonweg elke keer op "Next".

Gebruikers van Mac OS installeren dit best via of in een virtuele machine. Als dit echt niet lukt, kunnen ze tijdelijk gebruiken, maar deze software werkt wat anders en ondersteunt geen arrays.

Een voorbeeld van hoe je begint te werken met Flowgorithm kan je hier bekijken.

https://visualstudio.microsoft.com/downloads/
https://dotnet.microsoft.com/en-us/download/dotnet/5.0
deze pagina
Wine
FlowRun
https://www.youtube.com/watch?v=7SuYTvYCqOw

Mee helpen?

Als je aanpassingen wil voorstellen specifiek voor de graduaatsopleiding, dan wordt aangeraden een fork van deze repository te maken op Github. De verschillen tussen de cursus voor de graduaatsopleiding en de bachelor hebben bijna uitsluitend betrekking tot de oefeningen. Als je aanpassingen wil doen met betrekking tot de inhoud, zie dan .

de overeenkomstige pagina in de cursus van de bachelor

Introductie tot C#

Introductie tot programmeren met C-Sharp

Het algoritme

De essentie van een computerprogramma is het zogenaamde algoritme (het "recept" zeg maar). Dit is de reeks instructies die het programma moet uitvoeren telkens het wordt opgestart. Het algoritme van een programma moet je zelf verzinnen. De volgorde waarin de instructies worden uitgevoerd zijn uiteraard zeer belangrijk. Dit is exact hetzelfde als in het echte leven: een algoritme om je fiets op te pompen kan zijn:

1. Haal dop van het ventiel
2. Plaats pomp op ventiel
3. Begin te pompen

Eender welke andere volgorde van bovenstaande algoritme zal vreemde (en soms fatale) fouten geven.

C-Sharp

Om een algoritme te schrijven dat onze computer begrijpt dienen we een programmeertaal te gebruiken. Net zoals er ontelbare spreektalen in de wereld zijn, zijn er ook vele programmeertalen. C# (spreek uit 'siesjarp') is er een van de vele. In tegenstelling tot onze spreektalen moet een computertaal 'exact' zijn en moet het op ondubbelzinnige manier door de computer verstaan worden. C# is een taal die deel uitmaakt van de .NET (spreek uit 'dotnet') omgeving die meer dan 15 jaar geleden door Microsoft werd ontwikkeld (juli 2000).

Met .NET Core kun je zowel webapplicaties, desktopapplicaties, microservices en Internet of Things toepassingen ontwikkelen.

Deze cursus werd geschreven in . Helaas ondersteunt deze geen # in titels. Daardoor heet dit hoofdstuk "C-Sharp" en niet "C#". Niets aan te doen.

De geschiedenis en de hele .NET-wereld vertellen zou een cursus op zich betekenen en gaan we hier niet doen. Het is nuttig om weten dat er een gigantische bron aan informatie over .NET en C# online te vinden is, beginnende met .

Markdown
docs.microsoft.com

Een C# project maken in Visual Studio

We gebruiken Visual Studio om een C# programma te ontwikkelen. In dit onderdeel leren we hoe je een C# project opstart.

Het belang van het maken van afspraken voor het geven namen

  1. We kunnen niet genoeg benadrukken hoe belangrijk het is om afspraken te maken en te volgen bij het schrijven van programma's. Goede afspraken zorgen ervoor dat je:

    1. code gemakkelijk kan delen met andere programmeurs; zonder afspraken kan je niet in een team werken;

    2. veel sneller fouten in je programma zal vinden;

    3. code leesbaar blijft, ook als je maanden later opnieuw bekijkt;

    4. sneller en beter gaan leren programmeren;

  2. We vinden het warm water niet opnieuw uit en volgen de richtlijnen van Microsoft:

  3. De basis voor de naamgevingsafspraken zijn:

    1. PascalNotatie: geen spaties tussen de woorden en alle woorden beginnen met een hoofdletter: EenProgrammaOmJeGoedBijTeVoelen

    2. camelCasenotatie: geen spaties tussen de woorden en alle woorden, behalve het eerste, beginnen met een hoofdletter, bijvoorbeeld: eenProgrammaOmJeGoedBijTeVoelen

Rootmap

Ik raad jullie aan een rootmap te maken voor alle vakken die je op AP volgt. Je geeft die bijvoorbeeld de naam "AP" of "Hogeschool". Daarin maak je een map met de naam Programmeren Intro. In deze map zullen we alle projecten plaatsen die we in dit vak zullen maken.

een overzicht van alle afspraken vind je op

een overzicht van algemene afspraken, wat hier van toepassing is, vind je op

Voor vakken waarin met Git gewerkt wordt, vermijden we mappen die automatisch gesynchroniseerd worden. Zet je rootmap dus niet in OneDrive of Dropbox of Google Drive. Dat kan voor technische problemen zorgen. Meer info vind je in .

Naming Guidelines
General Naming Conventions
de cursus Git
697B
programmeren.snippet

Fouten in je code

Je code kan niet gecompileerd en uitgevoerd worden zolang er fouten in je code staan.

Opzettelijk een fout maken

We willen zien wat er gebeurt als er een fout in het programma staat. We verwijderen het puntkomma aan het einde van het statement in de Main functie. We vervangen

Console.WriteLine("Hello World!");

door

Console.WriteLine("Hello World!")

Het enige dat we hebben gewijzigd is de puntkomma op het einde.

Opzettelijke fout testen

Probeer het programma opnieuw uit te voeren. Je krijgt een melding dat er iets mis is. Vink eerst aan dat er onthouden moet worden wat je nu doet en geef dan aan dat je de fouten wil tonen via "Show Errors". Dit geeft je een overzicht, waarin je opmerkt dat er iets niet klopt op de regel en in de kolom waar de puntkomma ontbreekt. In de screenshot staat [9,46] en dat wijst op regel 9 van de code, positie 46. Waar de puntkomma moet staan, dus.

Je ziet ook dat het haakje net na "World!" rood onderlijnd is. Dat wijst op de positie van de fout. Let op: ook hier weet de ontwikkelomgeving niet precies wat je uiteindelijk wil, dus zie dit eerder als een hint.

Fout corrigeren

Plaats de puntkomma opnieuw op het einde van het statement op regel 9.

Opnieuw testen

Voer het programma opnieuw uit.

Meest voorkomende fouten

De meest voorkomende fouten in deze eerste weken zullen zijn:

  • Puntkomma vergeten.

  • Schrijffouten in je code RaedLine i.p.v. ReadLine.

  • Geen rekening gehouden met hoofdletter gevoeligheid Readline i.p.v. ReadLine (zie volgende hoofdstuk).

  • Per ongeluk accolades verwijderd.

  • Code geschreven op plekken waar dat niet mag.

De syntaxis van C#

Statements en de C# syntax

Om een uitvoerbaar C#-programma te maken moeten we bepaalde regels respecteren. We noemen deze regels de syntaxis of syntax van de taal.

Een C#-programma bestaat uit een opeenvolging van instructies ook wel statements genoemd. Met de uitzondering van blokken (ook blocks, afgebakend door { en }) eindigen deze steeds met een puntkomma (;)

De volgorde van de woorden is niet vrijblijvend en moet aan grammaticale regels voldoen. Enkel indien alle statements correct zijn zal het programma gecompileerd worden naar een werkend en uitvoerbaar programma (zoals in een vorige sectie besproken).

Enkele belangrijke regels van C#:

  • Hoofdletter-gevoelig: C# is hoofdlettergevoelig. Dat wil zeggen dat hoofdletter T en kleine letter t totaal verschillende zaken zijn voor C#. Reinhardt en reinhardt zijn dus ook niet hetzelfde.

  • Statements afsluiten met puntkomma: Ieder C# statement dat geen block is, wordt afgesloten moet een puntkomma ( ; ). Doe je dat niet dan zal C# denken dat de regel gewoon op de volgende lijn doorloopt en deze als één (fout) geheel proberen te compileren.

  • Blocks: Een block begint met {, eindigt met } en bevat daartussen verdere statements (ook geneste blocks zijn toegelaten).

  • Witruimtes: Spaties, tabs en newlines worden door de C# compiler genegeerd. Je kan ze dus gebruiken om de layout van je code te verbeteren. De enige plek waar witruimtes wél een verschil geven is tussen aanhalingstekens " " die we later (bij string) zullen leren gebruiken.

  • Commentaar toevoegen kan: met behulp van // voor een enkele lijn en /* */ voor meerdere lijnen commentaar. Commentaar zal door de compiler genegeerd worden.

Keywords

Er zijn binnen C# dan ook 80 woorden, zogenaamde reserved keywords die deel van de taal zelf uitmaken. In deze cursus zullen we stelselmatig deze keywords leren kennen en gebruiken op een correcte manier om zo werkende code te maken.

Deze keywords zijn:

De keywords in vet zijn keywords die we dit semester zullen kennen. Die in cursief in het tweede semester. De overige zal je zelf moeten leren.

Indien je deze tabel in pdf bekijkt zal deze om zeep zijn. Onze gitbook gnomes proberen dit op te lossen maar voorlopig vinden ze helaas geen oplossing, waarvoor onze excuses.

Variabelen, identifiers en naamgeving

We hebben variabelen nodig om data een naam te geven, zodat we er later naar kunnen verwijzen. Wanneer we een statement schrijven dat bijvoorbeeld input van de gebruiker moet vragen, dan willen we die input ook bewaren. Zo kunnen we verderop in het programma iets met deze data doen. We doen hetzelfde in ons hoofd wanneer we bijvoorbeeld zeggen: "tel 3 en 4 op en vermenigvuldig dat resultaat met 5". In die zin is "dat resultaat" een manier om te verwijzen naar wat we eerder gedaan hebben. Met andere woorden, een variabele. Vervolgens zullen we de inhoud van die variabele vermenigvuldigen met 5.

Wanneer we een variabele aanmaken, zal deze moeten voldoen aan enkele afspraken. Zo moeten we minstens 2 zaken aangeven:

  • Het type van de variabele: het datatype dat aangeeft wat voor data we wensen op te slaan (tekst, getal, afbeelding, etc.).

  • De naam van de variabele: de identifier waarmee we snel aan de variabele-waarde kunnen.

Regels voor identifiers

De code die we gaan schrijven moet voldoen aan een hoop regels. Wanneer we in onze code zelf namen (identifiers) moeten geven aan variabelen (en later ook methoden, objecten, etc.) dan moeten we een aantal regels volgen:

  • Hoofdlettergevoelig: de identifiers tim en Tim zijn verschillend, zoals reeds vermeld.

  • Geen keywords: identifiers mogen geen gereserveerde C# keywords zijn. De keywords van hierboven mogen dus niet. Varianten waarbij de hoofdletters anders zijn mogen wel, bijvoorbeeld: gOTO en stRINg mogen dus wel, maar niet goto of string daar beide een gereserveerd keyword zijn maar dankzij de hoofdlettergevoelig-regel is dit dus toegelaten. INT mag ook ook, maar niet int.

  • Eerste karakter-regel: het eerste karakter van de identifier mag enkel zijn:

    • kleine of grote letter

    • liggend streepje (_)

  • Alle andere karakters-regels: de overige karakters mogen enkel zijn:

    • kleine of grote letter

    • liggend streepje

    • een cijfer (0 tot en met 9)

  • Lengte: Een legale identifier mag zo lang zijn als je wenst, maar je houdt het best leesbaar.

Enkele voorbeelden

Enkele voorbeelden van toegelaten en niet toegelaten identifiers:

Naamgeving afspraken

  • Duidelijke naam: de identifier moet duidelijk maken waarvoor de identifier dient. Schrijf dus liever gewicht of leeftijd in plaats van a of meuh.

  • Camel casing: gebruik camel casing indien je meerdere woorden in je identifier wenst te gebruiken. Camel casing wil zeggen dat ieder nieuw woord terug met een hoofdletter begint. Een goed voorbeeld kan dus zijn leeftijdTimDams of aantalLeerlingenKlas1EA. Merk op dat we liefst het eerste woord met kleine letter starten. Uiteraard zijn er geen spaties toegelaten.

  • Pascal casing: Zoals camel casing, maar ook de eerste letter is een hoofdletter. Gebruik dit voor namespaces, klassen en voor methodes waarbij je public zet.

Commentaar

Soms wil je misschien extra opmerkingen bij je code zetten. Als je dat gewoon zou doen (bv Dit deel zal alles verwijderen), dan zal je compiler niet begrijpen wat die zin doet. Hij verwacht namelijk C# en geen Nederlandstalige zin. Om dit op te lossen kan je in je code op twee manieren aangeven dat een stuk tekst gewoon commentaar is en mag genegeerd worden door de compiler:

Enkele lijn commentaar

Eén lijn commentaar geef je aan door de lijn te starten met twee voorwaartse slashes //. Uiteraard mag je ook meerdere lijnen op deze manier in commentaar zetten. Zo wordt dit ook vaak gebruikt om tijdelijk een stuk code "uit te schakelen". Ook mogen we commentaar achter een stuk C# code plaatsen (zie voorbeeld hieronder).

Blok commentaar

We kunnen een stuk tekst als commentaar aangeven door voor de tekst /* te plaatsen en */ achteraan. Een voorbeeld:

Afspraken code

Voor de graduaatsopleiding volgen we een reeks afspraken (ook "conventies" genoemd) die bepalen welke code wel en niet aanvaardbaar is. Wanneer een groep programmeurs dezelfde afspraken volgt, is het voor iedereen makkelijker code uit te wisselen.

Als je deze pagina voor de eerste keer ziet, zullen de meeste van deze afspraken je nog niet veel zeggen. Dat is logisch. We zullen je tijdens de les wijzen op de afspraken die horen bij een nieuw concept, maar hier heb je een handig overzicht:

naamgeving

  • We kiezen namen in het Engels. Enkel tekst die aan de gebruiker getoond wordt, schrijven we in het Nederlands.

  • Merk op dat het verschil tussen PascalCase en camelCase samenhangt met de default zichtbaarheid. Wanneer afgeweken wordt van de default zichtbaarheid, kan de conventie voor de notatie ook veranderen. Wij zullen ons in deze cursussen voor de eenvoud strikt aan de default zichtbaarheid houden.

  • "meervoud toegestaan" betekent dat we iets noteren als een meervoud als de data ook meerdere objecten voorstelt. Bijvoorbeeld een lokale variabele met naam studenten en datatype Student[], maar een lokale variabele student met datatype Student.

  • We beginnen niet elke identifier voor een private member met een underscore. Sommige programmeurs doen dit wel, maar wij volgen één richtlijn.

  • Namen van klassen voor custom exceptions eindigen op Exception.

  • Vermijd afkortingen in namen.

    • Uitzondering: algemeen aanvaarde afkortingen zoals ID, HTML,...

    • Afkortingen van één of twee letters

  • Andere talen hebben soms andere conventies!

codeerstijl

using directieven

  • Deze komen alleen vooraan in het bestand voor en worden steeds gevolgd door de declaratie van de namespace.

namespaces

  • In het eerste semester groeperen we al onze code in de namespace Programmeren, in het tweede semester in de namespace ObjectgerichtProgrammeren.

een datatype per bestand

  • Elk zelf gedefinieerd datatype (klasse, struct, interface, enum type, later ook delegate) plaatsen we in een afzonderlijk bestand met dezelfde naam als dat datatype.

strings bouwen

  • In de eerste twee lessen mag dit met behulp van +.

  • Gebruik stringinterpolatie om kleine aantallen strings aan elkaar te hangen of data weer te geven in stringformaat.

  • Gebruik een StringBuilder om strings aan elkaar te hangen in een lus.

types declareren

  • Gebruik steeds een expliciet, statisch type.

    • Dat wil zeggen: geen variabelen declareren als var. Dat doe je pas later.

    • Dat wil zeggen: geen variabelen declareren als dynamic. Dat doe je pas later.

arrays

  • Gebruik de kortste syntax voor arrays van literals, dus string[] vowels1 = { "a", "e", "i", "o", "u" }; en niet string[] vowels2 = new string[] { "a", "e", "i", "o", "u" };.

static members

  • Plaats voor een static member altijd uitdrukkelijk de naam van de klasse waarin dat static member gedefinieerd is.

  • Klassen die uitsluitend static members bevatten ("library classes") maken we ook static met de syntax static class.

vergrendelen van data

  • Klassen die niet bedoeld zijn om van over te erven maken we final.

  • Als iets een constante is voor een bepaald programme, gebruiken we ook de access modifier const.

algemeen

We gebruiken alleen zaken die in de les aan bod zijn gekomen. We zien geen lambda's, delegates, LINQ,... dus je gebruikt deze ook niet, zelfs als je ze al ergens anders gezien hebt.

Variabelen

Variabelen

In het vorige hoofdstuk zagen we dat er verschillende datatypes bestaan. Deze types hebben we nodig om variabelen aan te maken. Een variabele is een koppeling van een naam aan gegevens. In C# heeft elke variabele ook een type.

Een variabele wordt bijgehouden in het geheugen van je machine, maar in een programmeertaal als C# vragen we ons niet af waar in het geheugen. In plaats daarvan gebruiken we de naam van de variabele, de zogenaamde identifier, om de gekoppelde gegevens op te vragen.

De naam (identifier) van de variabele moet voldoen aan de identifier regels onder "Inleiding -> Afspraken code".

Variabelen aanmaken en gebruiken

Om een variabele te maken moeten we deze declareren, door een type en naam te geven. Vanaf dan zal de computer een hoeveelheid geheugen voor je reserveren. Hiervoor dien je op te geven:

  1. Het datatype (bv int, double).

Een variabele declaratie heeft als syntax:

Bijvoorbeeld: int leeftijd;

Je mag ook meerdere variabelen van het zelfde datatype in 1 enkele declaratie aanmaken door deze met komma's te scheiden:

Bijvoorbeeld string voornaam, achternaam, adres;

Indien je reeds weet wat de beginwaarde moet zijn van de variabele dan mag je de variabele ook reeds deze waarde toekennen bij het aanmaken. Dit noemen we de initialisatie van de variabele.

Eens een variabele is geïnitaliseerd, kunnen we deze (op de meeste plaatsen) gebruiken alsof we de gekoppelde waarde rechtstreeks gebruikten.

Waarden toekennen aan variabelen

Een initialisatie is een speciaal geval van een toekenning. Een toekenning houdt in dat je de waarde die bij een bepaalde naam hoort instelt. In C# mag dit ook indien de variabele al een waarde heeft.

Met de toekennings-operator (=) kan je een waarde toekennen aan een variabele. Hierbij kan je zowel een letterlijke waarde toekennen oftewel het resultaat van een berekening (een "expressie").

Je kan ook een waarde uit een variabele uitlezen en toewijzen aan een andere variabele:

Literal toewijzen

Literals (of "letterlijke waarden") zijn expliciet uitgeschreven waarden in je code. Als je in je code expliciet de waarde 4 wilt toekennen aan een variabele dan is het getal 4 in je code een zogenaamde literal. Wanneer we echter data bijvoorbeeld eerst uitlezen of berekenen (via bijvoorbeeld invoer van de gebruiker of als resultaat van een berekening) en het resultaat hiervan toekennen aan een variabele dan is dit geen literal.

Voorbeelden van een literal toekennen:

Het is belangrijk dat het type van de literal overeenstemt met dat van de variabele waaraan je deze zal toewijzen. Een string literal stel je voor met aanhalingstekens. Volgende code zal dan ook een compiler-fout generen, daar je een string literal aan een int-variabele wil toewijzen, en vice versa.

Als je bovenstaande probeert te compileren dan krijg je volgende error-boodschappen:

Literal bepaalt het datatype

De manier waarop je een literal schrijft in je code zal bepalen wat het datatype van de literal is:

  • Gehele getallen worden standaard als int beschouwd, vb: 125.

  • Kommagetallen (met punt .) worden standaard als double beschouwd, vb: 12.5.

  • Via een suffix na het getal kan je aangeven als het om andere types gaat:

    • U of u voor uint, vb: 125U.

    • L of l voor long, vb: 125L.

    • UL of ul voor ulong, vb: 125ul.

    • F of f voor float, vb: 12.5f.

    • M of m voor decimal, vb: 12.5M.

  • Voor bool (zie verder) is dit enkel true of false.

  • Voor char (zie verder) wordt dit aangeduid met een enkele apostrof voor en na de literal, vb: 'q'.

  • Voor string (zie verder) wordt dit aangeduid met aanhalingsteken voor en na de literal, vb: "pikachu".

De overige types sbyte, short en ushort hebben geen literal aanduiding. Er wordt vanuit gegaan wanneer je een literal probeert toe te wijzen aan een van deze types dat dit zonder problemen zal gaan (ze worden impliciet geconverteerd).

Nieuwe waarden overschrijven oude waarden

Wanneer je een reeds gedeclareerde variabele een nieuwe waarde toekent dan zal de oude waarde in die variabele onherroepelijk verloren zijn. Probeer dus altijd goed op te letten of je de oude waarde nog nodig hebt of niet. Wil je de oude waarde ook nog bewaren dan zal je een nieuwe, extra variabele moeten aanmaken en daarin de nieuwe waarde moeten bewaren:

In dit voorbeeld zal er dus voor gezorgd worden dat de oude waarde van temperatuurGisteren, 20, overschreven zal worden met 25.

Volgende code toont hoe je bijvoorbeeld eerst de vorige waarde kunt bewaren en dan overschrijven:

We hebben dus aan het einde van het programma zowel de temperatuur van eergisteren, 20, als die van vandaag, 25.

Je eerste stappen in C#

In dit onderdeel analyseren we het standaard programma dat Visual Studio voor ons in het bestand Program.cs gemaakt heeft. Verder zetten we de eerste stappen in het leren programmeren.

Doelstelling

  1. weet je hoe de basisstructuur van een C# programma in elkaar steekt

  2. ken je de basiselementen van het programmeren:

    1. declaratie: gegevens en variabelen

    2. statement: één instructie

    3. functie of methode: een reeks instructies om één specifiek doel te realiseren, bijvoorbeeld voor het bereken van de oppervlakte van een cirkel In Object Oriented jargon spreekt men van een methode en vermits C# een OO taal is, zullen we in deze cursus verder het woord methode gebruiken. Als jullie JavaScript leren ga je het niet over een methode hebben maar over een functie omdat JavaScript geen strikte OO taal is.

De basisstructuur van een C# programma

Schema

Detail

Beschrijving van bovenstaande basisstructuur

  1. methode In C# staat code altijd in een methode. Je kan geen statement schrijven zonder eerst een methode te hebben gemaakt. Hier moeten enkele zaken worden opgemerkt:

    1. static Het sleutelwoord static is een modifier. Met een modifier geven we aan hoe we de methode willen gebruiken. Vermits we in het eerste semester klassen enkel en alleen gaan gebruiken om code te ordenen en niet om objecten te maken en te gebruiken, gaan we meestal de modifier static vóór de methode naam plaatsen, zoals in het voorbeeld hierboven. Met static bedoelen we dan deze methode tot de klasse behoort en niet tot de objecten die op basis van die klasse worden gemaakt.

    2. void In C# spreken we van methoden. Maar een methode is eigenlijk een functie. Een functie retourneert meestal een waarde. Is dat niet zo moet je aangegeven dat de methode geen waarde retourneert door er void vóór de plaatsen zoals de Main methode in het voorbeeld hierboven.

    3. Main Dit is de naam van de methode. De moeder aller methoden in een C# programma draagt de naam Main methode. Elk programma dat je ooit gaat schrijven in C# moet een Main methode bevatten. Het is bovendien de enige methode die zal worden uitgevoerd als je het programma runt. Alle andere methode worden niet automatisch uitgevoerd. Wil je een bepaalde methode laten uitvoeren moet je die in de Main methode oproepen.

    4. (string[] args) Na de naam staan twee ronde haakjes. Tussen de ronde haken kan je parameters plaatsen. In deze parameters worden de argumenten die bij het oproepen van de methode worden meegegeven opgevangen. In het voorbeeld hierboven is dat string[] args. Dat wil zeggen dat je de Main methode kan oproepen en een reeks van argumenten kan meegeven. We komen hier later op terug. Je mag deze parameter gerust verwijderen vermits we pas later leren werken array's. De Main methode hierboven bevat slechts 1 statement.

  2. statement Er is maar 1 statement. In het statement roepen we de WriteLine methode op die in de klasse Console staat. De klasse Console staat in de namespace met de naam System. Bovenaan, op regel 1, geven we aan dat we die bibliotheek in ons programma zullen gebruiken met het statement using System; Het statement ziet er zo uit:

    Het eerste wat we opmerken is dat een statement eindigt met een ; (een puntkomma). In dit statement roepen we de methode WriteLine op en geven als argument de tekst "Hello World" mee. De methode WriteLine beschikt over 1 parameter die deze tekst aanneemt en voor ons in de console zal tonen.

Let erop dat je iedere 'zin' eindigt met een puntkomma.

Nuttige extras

Nuttige extra's

Boeken

Er zijn quasi oneindig veel boeken over C# geschreven, althans zo lijkt het. Hier een selectie van boeken met een korte bespreking waarom ik denk dat ze voor jou een meerwaarde kunnen zijn bij het leren programmeren in C#:

Beginner boeken

Geavanceerd

Online

Leren programmeren door enkele de opdrachten in deze cursus te maken zal je niet ver (genoeg) brengen. Onze dikke vriend het Internet heeft echter tal van schitterende bronnen. Hier een overzicht.

Cheat sheet

Game-based programmeren

Ideale manier om programmeren meer in de vingers te krijgen op een speelse manier:

Apps

Websites

Tutorials

Oefenvragen

  • Pittige vragen van de jaarlijkse Vlaamse Programmeerwedstrijd:

Streaming programmeurs

Ja hoor, ze bestaan. Meer en meer professionele én beginnende programmeurs streamen terwijl te programmeren. Dit is een ideale manier om te zien hoe andere mensen problemen aanpakken. De meeste programming streamers kan je terugvinden op youtube, maar ook op Twitch zijn er steeds meer. Enkele aanraders (bekijk zeker de filmpjes uit de archieven eens):

Strings

String

Een string is een reeks van 0, 1 of meerdere lettertekens, zoals je ook kan zien als je even met je muis boven een string keyword hovert in je code:

Strings declareren

Merk op dat we bij een string literal gebruik maken van aanhalingstekens (") terwijl bij chars we een apostrof gebruiken ('). Dit is de manier om een string van een char te onderscheiden.

Volgende code geeft twee keer het cijfer 1 onder elkaar op het scherm, maar de eerste keer gaat het om de weergave van een string (reeks van tekens) en de tweede keer van een int (effectief getal):

De output van dit programma zal dan zijn:

Fout gebruik van strings zal code geven die niet zal gecompileerd worden:

  1. In de tweede toekenning proberen we een literal van het type int toe te kennen aan een variabele van het type string.

  2. In de laatste toekenning proberen we een literal van het type string toe te kennen aan een variabele van het type int.

camelCase
Je settings moeten er zo uitzien.
Je settings moeten er zo uitzien.

De verschillende datatypes bespreken we in een volgend .

Er zijn geen vaste afspraken over hoe je je variabelen moet noemen toch hanteren we enkele coding guidelines die doorheen je opleiding moeten gevolgd worden. Naarmate we meer C# leren zullen er extra guidelines bijkomen (zie ).

Onderstaande richtlijnen zijn gebaseerd op . We beperken ons tot de zaken die we tijdens de cursus zien.

We baseren ons op . Je hoeft deze conventies niet te lezen. Hieronder volgt een vereenvoudigd overzicht van de afspraken die aan bod kunnen komen in de cursus en enkele eigen conventies:

Een identifier zodat de variabele uniek kan geïdentificeerd worden ().

sleutelwoorden (keywords) In de editor zie je dat sommige woorden in blauw worden weergegeven. Visual Studio geeft alle sleutelwoorden in het blauw weergeven. Sleutelwoorden zijn vooraf gedefinieerde, gereserveerde woorden die een speciale betekenis hebben voor de C# compiler. Je kan ze niet als namen (identifiers) in je programma gebruiken. Een lijst van alle gereserveerde sleutelwoorden vind je op .

namespace Letterlijk vertaald is dat een naamruimte. Deze structuur biedt de mogelijkheid om namen van o.a. klassen en methode - in programmeerjargon spreekt men van identifiers - in een naamspace te groeperen. Je moet er dan enkel voor zorgen dat deze namen uniek zijn binnen de namespace. De regels en afspraken voor het geven van namen aan namespaces vind je op . Je kan een namespace vergelijken met een familienaam. Binnen de 'naamruimte' van een familienaam moeten de voornamen uniek zijn. Maar dezelfde voornaamen kunnen in verschillende familienamen gebruikt worden: De namespace in het voorbeeld hierboven is BeginnenMetCSharp. Visual Studio gebruikt de naam van het project voor het benoemen van deze namespace.

klasse Gegevens (data) en algoritmes (programma's) worden ondergebracht in klassen. Dit maakt een programma overzichtelijker. In het tweede semester gaan we dieper in op klassen en objecten ('exemplaren' van een klasse). In het eerste semester gebruiken we klassen alleen om code te ordenen. De klassenaam in het voorbeeld hierboven is Program. Deze klassenaam wordt door Visual Studio standaard gebruikt wanneer je een nieuw project aanmaakt. De regels en afspraken voor het geven van namen aan klassen vind je op .

naamgeving De namen van methoden worden in pascalnotatie geschreven, meer info op .

van Mike McGrath: een uiterst compact, maar zeer helder en kleurrijk boekje dat ik ten stelligste aanbeveel als je wat last hebt met de materie van de eerste weken.

van Joyce Farrell: Niet het meest sexy boek, maar wel het meest volledige qua overlap met de leerstof van deze cursus. Aanrader voor zij die wat meer in detail willen gaan en op zoek zijn naar oneindig veel potentiele examenvragen ;)

van Andrew Stellman & Jennifer Greene: laat de ietwat bizarre, bijna kleuterachtige look and feel van de head first boeken je niet afschrikken. Ieder boek in deze serie is goud waar. De head first boeken zijn d� ideale manier als je zoekt naar een alternatieve manier om complexe materie te begrijpen. Bekijk zeker ook de Head First Design Patterns en Head First Sql boeken in de reeks!

van Bart De Smet: in mijn opinie dé referentie om C# tot op het bot te begrijpen. Geschreven door een Belg die bij Microsoft in Redmond aan C# werkt.

van Steve McConnell: een referentiewerk over 'programmeren in het algemeen'. Het boek is al jaar en dag het te lezen boek als je je als programmeur wilt verdiepen in wat nu 'correct programmeren' behelst. Als je op je CV kunt zetten dat je dit boek door en door kent dan zal elk IT-bedrijf je stante pede aannemen ;)

Volgende document bevat een overzicht van de basis C# syntax zaken van het eerste en (deel van het) tweede semester:

: Verplichte app! Simple as that!

Net zoals SoloLearn maar dan anders.

(aanrader!) Origineel vooral bedoeld om spreektalen te leren, maar bevat ook tal van andere zaken. Hoofdzakelijk nuttig om nieuwe aspecten te 'drillen'. Enkel dus nuttig indien je de basismaterie eerst hebt verwerkt. Bekijk zeker ook de tal van andere cursussen die er staan. Let er op dat je bij de filter Engels instelt, er zijn nog niet veel (goede) Nederlandstalige C# cursussen naar mijn weten. Opgelet: je kan je enkel via de browser inschrijven op niet-spreektaal-cursussen. De app toont enkel spreektaalcursussen.

Speels en vrij beperkt in gratis versie, maar ideale aanvulling op SoloLearn.

Een steam spel om te leren programmeren. Weliswaar Javascript (nuttig voor Web Programming) maar het concept is te cool om niet hier te vermelden en zoals je zal ontdekken: leren programmeren kan je in eender welke taal, en het zal ook je andere programmeer-ervaring verbeteren. Give it a go!

(specifiek voor C#!)

(behandelt leerstof van volledig eerste jaar en meer)

Pittige programmeeroefeningen die jaarlijks in december verschijnen.

Handig vorm gegeven gratis ebooks met tal van onderwerpen waaronder ook C# en het .NET Framework.

: De uitgebreidere, Engelstalige variant van deze cursus zeg maar.

: Aanrader.

: Zeer aan te raden indien je een bepaald concept uit de les niet begrijpt.

: Nederlandstalige cursus met veel toffe oefeningen waarvan je sommige zelfs in deze cursus zal terugvinden.

: Microsoft heeft een virtual academy cursus "C# fundamentals" uitgebracht. Ik kan deze cursus zeer erg aanbevelen.

: Zeer vermakelijk, vlot geschreven C# boek(je)

: "This site tries to gather open-source remakes of great old games in one place." Je vindt er ook tal van C# projecten terug zoals .Klik bovenaan op "languages" en filter maar eens op C#.

: nog steeds relevant.

: deze programmeur heeft een volledige RPG gemaakt en het hele proces gestreamd.

Kennisclip voor deze inhoud

abstract

as

base

bool

break

byte

case

catch

char

checked

class

const

continue

decimal

default

delegate

do

double

else

enum

event

explicit

extern

false

finally

fixed

float

for

foreach

goto

if

implicit

in

int

interface

internal

is

lock

long

namespace

new

null

object

operator

out

override

params

private

protected

public

readonly

ref

return

sbyte

sealed

short

sizeof

stackalloc

static

string

struct

switch

this

throw

true

try

typeof

uint

ulong

unchecked

unsafe

ushort

using

using static

virtual

void

volatile

while

identifier

toegelaten?

uitleg indien niet toegelaten

werknemer

ja

kerst2018

ja

pippo de clown

neen

geen spaties toegestaan

4dPlaats

neen

mag niet starten met getal

_ILOVE2019

ja

Tor+Bjorn

neen

enkel cijfers, letters en liggende streepjes toegestaan

ALLCAPSMAN

ja

B_A_L

ja

class

neen

gereserveerd keyword

WriteLine

ja

//De start van het programma
int getal=3;
//Nu gaan we rekenen
int result = getal * 5;
// result= 3*5;
Console.WriteLine(result); //We tonen resultaat op scherm: 15
/*
    Veel commentaar.
    Een heel verhaal
    Mooi he.
    Is dit een haiku?
*/
int leeftijd= 0;
leeftijd++;

Soort data

Notatie

Enkelvoud?

Vorm

Default zichtbaarheid

Klasse

PascalCase

altijd

[A-z]+[0-9]

public

Methode

PascalCase

meervoud toegestaan

[A-z]+[0-9]

public

Argument

camelCase

meervoud toegestaan

[A-z]+[0-9]

niet van toepassing

Lokale variabele

camelCase

meervoud toegestaan

[A-z]+[0-9]

niet van toepassing

Constante

PascalCase

altijd

[A-z]+[0-9]

public

Veld

camelCase

meervoud toegestaan

[A-z]+[0-9]

private

Property

PascalCase

meervoud toegestaan

[A-z]+[0-9]

public

Enum type

PascalCase

altijd

[A-z]+

public

Waarde enum

PascalCase

altijd

[A-z]+

niet van toepassing

datatype identifier;
datatype identifier1, identifier2, identifier3;
int mijnLeeftijd = 37;
int eenAndereLeeftijd = mijnLeeftijd;
int temperatuurGisteren = 20;
int temperatuurVandaag = 25;
string eenTekst;
int eenGetal;

eenTekst = 4;
eenGetal = "4";
int temperatuurGisteren = 20;
temperatuurGisteren = 25;
int temperatuurGisteren= 20;
//Doe vanalles
//...
//1 dag later
int temperatuurEerGisteren= temperatuurGisteren; //Vorige temperatuur in eergisteren bewaren
temperatuurGisteren = 25; //temperatuur nu overschrijven
Console.WriteLine("Hello World!");
string eenString = "1"; 
int eenGetal = 1;

Console.WriteLine(eenString);
Console.WriteLine(eenGetal);
1
1
string eenString = 1; //fout
int eenGetal = "1"; //fout

Functionaliteit van strings

Strings bevatten veel ingebouwde functionaliteit. Als je deze leert gebruiken, kan je al snel nuttige programmaatjes voor tekstverwerking schrijven.

Length

De lengte van een string is het aantal symbolen in de weergegeven versie. Je plaatst .Length achter de string om de lengte te weten te komen. Enkele voorbeelden:

Console.WriteLine("hallo".Length); // levert 5 want: 5 symbolen in de uiteindelijke weergave
Console.WriteLine("hallo ".Length); // levert 6 want: 6 symbolen in de uiteindelijke weergave

Substring

Een string is een reeks van 0 of meer symbolen in een bepaalde volgorde. Dat wil zeggen dat we elk symbool een nummer kunnen toekennen. Misschien wat vreemd, maar het eerste symbool krijgt nummer 0, het tweede krijgt nummer 1, het derde nummer 2,... en het laatste krijgt een nummer gelijk aan de Length van de string min één. Dit nummer heet de index van het symbool.

We kunnen een substring (= deel van een string) opvragen door de index van het eerste symbool en de lengte mee te geven als volgt:

Console.WriteLine("Hallo, wereld".Substring(4,5)); // toont o, we

Je mag de lengte achterwege laten om vanaf de gegeven index tot het einde van de string te gaan:

Console.WriteLine("Hallo, wereld".Substring(4)); // toont o, wereld

Deze methode verandert een string niet. Er is geen enkele methode die dat doet, want je kan een string niet veranderen in C#. Je kan er alleen een nieuwe mee bouwen. De methode berekent wel een nieuwe string met de gewenste eigenschappen. Dit is een belangrijk onderscheid. Volgende drie voorbeelden tonen het verschil. Voer uit en verklaar.

string hallo1 = "hallo";
hallo1.substring(0,2);
Console.WriteLine(hallo1);
string hallo1 = "hallo";
Console.WriteLine(hallo1.substring(0,2));
string hallo1 = "hallo";
string hallo2 = hallo1.substring(0,2);
Console.WriteLine(hallo2);

Onthoud het goed: je kan een string niet aanpassen in C#. We kunnen alle gevolgen hiervan nog niet uitleggen, maar het is wel zo.

IndexOf

Een index is, zoals hierboven aangegeven, de positie van een teken in de string. Als we willen weten waar een bepaalde substring in een string voorkomt, gebruiken we IndexOf:

Console.WriteLine("C# is cool".IndexOf("cool")); // geeft 6
Console.WriteLine("C# is cool".IndexOf("z")); // geeft -1 want komt niet voor
Console.WriteLine("C# is cool".IndexOf("Cool")); // geeft -1 want "Cool" MET HOOFDLETTER komt niet voor

ToUpper / ToLower

Met deze twee methodes bereken je een versie van de string waarop je ze toepast, maar dan in hoofdletters of in kleine letters. Let op: je past de oorspronkelijke string niet aan!

Console.WriteLine("C# is cool".ToUpper()); // C# IS COOL
Console.WriteLine("C# is cool".ToLower()); // c# is cool
string tekst = "DiT iS EnGe TeKsT";
tekst.ToLower();
Console.WriteLine(tekst); // DiT iS EnGe TeKsT -> tekst wordt niet aangepast door ToLower
tekst.ToUpper();
Console.WriteLine(tekst); // DiT iS EnGe TeKsT -> tekst wordt niet aangepast door ToLower
string berekendeTekst = tekst.ToUpper();
Console.WriteLine(berekendeTekst); // DIT IS ENGE TEKST -> uit tekst is iets anders berekend, wel in hoofdletters

Replace

Met deze methode kan je een substring vervangen door een andere substring. Ook deze methode past de oorspronkelijke tekst niet aan.

Console.WriteLine("C# is cool".Replace("C#","Racket")); // Racket is cool
Console.WriteLine("C# is cool".Replace("Java","Racket")); // C# is cool -> Java kwam niet voor dus is niet vervangen
string tekst = "C# is cool";
tekst.Replace("C#","Racket");
Console.WriteLine(tekst); // C# is cool -> uit tekst is iets anders berekend, tekst is niet aangepast

TrimStart / TrimEnd / Trim

Met deze methodes verwijder je witruimte (spaties, tabs, newlines,...) aan het begin of aan het einde van een string:

Console.WriteLine("     C# is cool".TrimStart()); // C# is cool, eerste teken is C en geen spatie
Console.WriteLine("C# is cool     ".TrimEnd()); // zelfde resultaat
Console.WriteLine("    C# is cool     ".Trim()); // zelfde resultaat
Kennisclip voor deze inhoud
hoofdstuk
deze pagina met richtlijnen
deze algemeen aanvaarde richtlijnen
de afspraken van Microsoft
Kennisclip voor deze inhoud
volgens de naamgevingsregel van C#
Kennisclip voor deze inhoud
C# Keywords
Names of Namespaces
Names of Classes, Structs, and Interfaces
Names of Methods
C# Programming
Microsoft Visual C# 2015: An introduction to OOP
Head First C#
C# Unleashed
Code Complete
download hier
SoloLearn
Enki
Memrise
Mimo
Screeps
Exercism
Coding game
Code Combat
Pex For Fun
Code Academy
RPG Game in C#
Advent of code
Free Programming Book
Tutorials teacher
Dotnet beginning
C# Getting started interactive quickstart tutorials
Online video c# cursus
C-sharp.be
Microsoft Virtual Academy
Rob Miles's The C# Programming Yellow book
Open Source Game Clones
GTA 2
Een lijst met oude oefenvragen uit 2010
Veel kleine, fijne oefeningen
Een dagelijkse programmeeruitdaging op reddit
2013
2014
2015
Handmade Hero
CSharpFrits
DevChatter
Visual Studio Twitch
NoopKat
The Coding train

Input/Output: ReadLine/WriteLine

Via Console.WriteLine kan je iets op het scherm laten zien:

using System;

namespace Demo1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            Console.WriteLine("Hoi, ik ben het");
            Console.WriteLine("Wie ben jij?!");
        }
    }
}

ReadLine: Input van de gebruiker verwerken

Met de console kan je met een handvol methoden reeds een aantal interessante dingen doen.

Zo kan je bijvoorbeeld input van de gebruiker inlezen en bewaren in een variabele als volgt:

string result;
result = Console.ReadLine();

Bespreking van deze code:

string result;

  • Concreet zeggen we hiermee aan de compiler: maak in het geheugen een plekje vrij waar enkel data van het type string in mag bewaard worden;

  • Noem deze geheugenplek result zodat we deze later makkelijk kunnen in en uitlezen.

result = Console.ReadLine();

  • Vervolgens roepen we de ReadLine methode aan. Deze methode zal de invoer van de gebruiker uitlezen tot de gebruiker op enter drukt.

  • Het resultaat van de ingevoerde tekst wordt bewaard in de variabele result (denk eraan dat de toekenning van rechts naar links gebeurt).

  • Een stukje code van de vorm naam = waarde heet een toekenning. Dit is niet hetzelfde als een wiskundige gelijkheid. In een toekenning plaats je een waarde aan de rechterkant. Aan de linkerkant schrijf je een naam die je aan deze waarde geeft.

  • Als we een naam voor de eerste keer in het programma koppelen aan een waarde, spreken we soms over een initialisatie. Dit is ook een soort toekenning.

Je programma zou nu moeten zijn:

namespace Demo1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hoi, ik ben het!");
            Console.WriteLine("Wie ben jij?!");
            string result;
            result = Console.ReadLine();
        }
    }
}

Start nogmaals je programma. Je zal merken dat je programma nu een cursor toont en wacht op invoer. Je kan nu eender wat intypen en van zodra je op enter duwt gaat het programma verder (in dit geval stopt het programma hierna dus).

Input gebruiker verwerken en gebruiken

We kunnen nu invoer van de gebruiker, die we hebben bewaard in de variabele result gebruiken en tonen op het scherm.

Console.WriteLine("Dag ");
Console.WriteLine(result);
Console.WriteLine(" hoe gaat het met je?");

Op de tweede lijn hier gebruiken we de variabele result (waar de invoer van de gebruiker in bewaard wordt) als parameter in de WriteLine-methode.

Met andere woorden: de WriteLine methode zal op het scherm tonen wat de gebruiker even daarvoor heeft ingevoerd.

Je volledige programma ziet er dus nu zo uit:

using System;

namespace Demo1
{
    class Program
    {
        static void Main(string[] args)
        {

            Console.WriteLine("Hoi, ik ben het!");
            Console.WriteLine("Wie ben jij?!");

            string result;
            result = Console.ReadLine();

            Console.WriteLine("Dag");
            Console.WriteLine(result);
            Console.WriteLine("hoe gaat het met je?");
        }
    }
}

Test het programma en voer je naam in wanneer de cursor knippert.

Voorbeelduitvoer (lijn 3 is wat de gebruiker heeft ingetypt)

Hoi, ik ben het!
Wie ben jij?!
tim [enter]
Dag
tim
hoe gaat het met je?

Aanhalingsteken of niet?

Wanneer je de inhoud van een variabele wil gebruiken in een methode zoals WriteLine() dan plaats je deze zonder aanhalingsteken! Bekijk zelf eens wat het verschil wordt wanneer je volgende lijn code Console.Write(result); vervangt door Console.Write("result");.

De uitvoer wordt dan:

Hoi, ik ben het!
Wie ben jij?!
tim [enter]
Dag
result
hoe gaat het met je?

Write en WriteLine

De WriteLine-methode zal steeds een line break (een 'enter' ) aan het einde van de lijn zetten zodat de cursor naar de volgende lijn springt.

De Write-methode zal geen enter aan het einde van de lijn toevoegen. Als je dus vervolgens iets toevoegt (met een volgende Write of WriteLine) dan zal dit aan dezelfde lijn toegevoegd worden.

Vervang daarom eens de laatste 3 lijnen code in je project door:

Console.Write("Dag");
Console.Write(result);
Console.Write("hoe gaat het met je?");

Voer je programma uit en test het resultaat. Je krijgt nu:

Hoi, ik ben het!
Wie ben jij?!
tim [enter]
Dagtimhoe gaat het met je?

Wat is er verkeerd gelopen? Al je tekst van de laatste lijn plakt zo dicht bij elkaar? Inderdaad, we zijn spaties vergeten toe te voegen! Spaties zijn ook tekens die op scherm moeten komen (ook al zien we ze niet) en je dient dus binnen de aanhalingstekens spaties toe te voegen. Namelijk:

Console.Write("Dag ");
Console.Write(result);
Console.Write(" hoe gaat het met je?");

Je uitvoer wordt nu:

Hoi, ik ben het!
Wie ben jij?!
tim [enter]
Dag tim hoe gaat het met je?

Opletten met spaties

Spaties zijn ook tekens die op scherm moeten komen (ook al zien we ze niet) en je dient dus binnen de aanhalingstekens spaties toe te voegen. Indien je deze erbuiten plaats dan heeft dit geen effect (je wist al uit het eerste hoofdstuk dat C# alle witregels negeert die niet tussen aanhalingstekens staan). In volgend voorbeeld zijn de spaties aangegeven als liggende streepjes ( _ ).

Fout (de code zal werken maar je spaties worden genegeerd):

Console.Write("Dag"_);
Console.Write(result_);
Console.Write("hoe gaat het met je?");

Correct:

Console.Write("Dag_");
Console.Write(result);
Console.Write("_hoe gaat het met je?");

Zinnen aan elkaar plakken

We kunnen dit hele verhaal een pak korter tonen. De plus-operator (+) in C# kan je namelijk gebruiken om variabelen van het type string aan elkaar te plakken. De laatste 3 lijnen code kunnen korter geschreven worden als volgt:

Console.WriteLine("Dag " + result + " hoe gaat het met je?");

Merk op dat result dus NIET tussen aanhalingstekens staat, in tegenstelling de andere stukken zin. Waarom is dit? Aanhalingstekens in C# duiden aan dat een stuk tekst moet beschouwd worden als tekst van het type string. Als je geen aanhalingsteken gebruikt dan zal C# de tekst beschouwen als een variabele met die naam.

Bekijk zelf eens wat het verschil wanneer je volgende lijn code vervangt door de lijn er onder:

Console.WriteLine("Dag "+ result + " hoe gaat het met je?");
Console.Write("Dag "+ "result" + " hoe gaat het met je?");

Meer input vragen

Als je meerdere inputs van de gebruiker tegelijkertijd wenst te bewaren dan zal je meerdere geheugenplekken nodig hebben om de invoer te bewaren. Bijvoorbeeld:

Console.WriteLine("Geef leeftijd");
string leeftijd;  //eerste geheugenplekje aanmaken
leeftijd = Console.ReadLine();
Console.WriteLine("Geef adres");
string adres; //tweede geheugenplekje aanmaken
adres = Console.ReadLine();

Je mag echter ook de geheugenplekken al vroeger maken. In C# zet men de geheugenplek creatie zo dicht mogelijk bij de code waar je die plek gebruikt (zoals vorig voorbeeld), maar dat is geen verplichting. Dit mag dus ook:

string leeftijd;  //eerste geheugenplekje aanmaken
string adres; //tweede geheugenplekje aanmaken
Console.WriteLine("Geef leeftijd");
leeftijd = Console.ReadLine();
Console.WriteLine("Geef adres");
adres = Console.ReadLine();

Oefeningen

Al deze oefeningen maak je in een klasse VariabelenEnDatatypes. In de oefeningen van hoofdstuk 1 heb je gezien hoe je een nieuwe klasse maakt.

Oefening: H2-optellen

Leerdoelen

  • gebruik van variabelen om input via Console.ReadLine op te slaan

  • berekeningen met de opgeslagen data uitvoeren

  • het resultaat dat werd opgeslagen in een variabele via Console.WriteLine te tonen

Functionele analyse

Een applicatie vraagt je twee getallen in te voeren. Na de invoer van het tweede getal worden beide getallen bij elkaar opgeteld. Het resultaat wordt uiteindelijk weergeven.

Technische analyse

Noem de methode voor deze oefening Optellen.

  1. De vraag wordt gesteld om een getal in te typen en daarna op enter/return te drukken.

  2. Er wordt gevraagd een tweede getal in te typen en dan op enter/return te drukken.

  3. De twee getallen worden opgeteld.

  4. Het resultaat wordt weergegeven.

voorbeeldinteractie(s)

Wat is het eerste getal?
> 1
Wat is het tweede getal?
> 4
De som is 5.

Technische hulp

Programmaverloop

Lees de gebruikersinvoer van de console en sla dit op in een variabele voor wat het eerste getal betreft. Herhaal dit voor het tweede getal. Tel de twee getallen samen en bewaar deze in een derde variabele. Uiteindelijk geef je dan de inhoud van deze derde variabele weer in de console.

Let op: met Console.ReadLine() lees je tekst in, dus waarden die je kan toekennen aan variabelen van type string. Om een getal in te lezen, vervang je Console.ReadLine() door Convert.ToInt32(Console.ReadLine()). De werking hiervan zie je later.

Testscenario's

  • Voer tekst in.

  • Voer een getal met 100 cijfers in.

  • Voer geen getal in.

Oefening: H2-verbruik-wagen

Leerdoelen

  • gebruik van variabelen om input via Console.ReadLine op te slaan

  • berekeningen met de opgeslagen data uitvoeren

  • het resultaat dat werd opgeslagen in een variabele via onsole.WriteLine te tonen

Functionele analyse

Een applicatie zal voor jou het gemiddelde verbruik van een wagen berekenen.

Hiervoor worden volgende vragen gesteld:

  1. Hoeveel liter is er nog aanwezig in de benzinetank.

  2. Hoeveel liter zit er nog in de benzinetank na de rit.

  3. Ook de kilometerstand van bij de aanvang van de rit wordt gevraagd en ook deze nadat de rit werd uitgevoerd.

Op basis van deze parameters wordt het gemiddelde verbruik berekend en weergegeven.

Technische analyse

Noem de methode voor deze oefening VerbruikWagen.

  1. De vraag wordt gesteld om het aantal liter, aanwezig in de benzinetank, op te geven.

  2. Daarna wordt gevraagd om ook het aantal liter op te geven na de rit.

  3. De kilometerstand van de aanvang van de rit wordt gevraagd.

  4. Uiteindelijk ook de kilometerstand na het beëindigen van de rit wordt opgevraagd.

voorbeeldinteractie(s)

Technische hulp

Programmaverloop

Lees de gebruikersinvoer van de console en slaag dit op in variabelen.

Zorg ervoor dat je het juiste gegevenstype kiest voor de verschillende variabelen.

Nadien voer je de berekening uit om op basis van de ingevoerde gegevens het gemiddeld verbruik te berekenen (100 * (aantalLiterinTankVoorRit - aantalLiterinTankNaRit) / (kilometerstandNaRit - kilometerstandVoorRit))

Uiteindelijk geef je dan het resultaat weer in de console.

Testscenario's

  • Voer tekst in.

  • Voer een getal met 100 cijfers in.

  • Voer geen getal in.

Oefening: H2-beetje-wiskunde

Leerdoelen

  • expressies schrijven

  • voorrang van operatoren

  • effect van operaties naargelang datatype begrijpen

Functionele analyse

Je schrijft een programma dat de rol vervult van een rekenmachine. Het voert volgende berekeningen uit:

  • -1 + 4 * 6

  • ( 35+ 5 ) * 7

  • 14 + -4 * 6 / 11

  • 2 + 15 / 6 * 1 - 7 * 2

Technische analyse

Noem de methode voor deze oefening BeetjeWiskunde.

voorbeeldinteractie(s)

23
280
12
-10

Technische hulp

Programmaverloop

Eerst wordt een resultaat berekend, daarna wordt het geprint.

Testscenario's

  • Test uit met getallen van het type int.

  • Test uit met getallen van het type float.

Ondersteunend materiaal

Oefening: H2-gemiddelde

Leerdoelen

  • expressies schrijven

  • voorrang van operatoren

  • effect van operaties naargelang datatype begrijpen

Functionele analyse

Je schrijft een programma dat het gemiddelde van 18, 11 en 8 berekent, d.w.z. deze drie getallen optelt en de som deelt door drie.

Technische analyse

Noem de methode voor deze oefening Gemiddelde.

voorbeeldinteractie(s)

12

Technische hulp

Programmaverloop

Eerst wordt het resultaat berekend, daarna wordt het geprint.

Testscenario's

  • Test uit met getallen van het type int.

  • Test uit met getallen van het type float.

Oefening: H2-maaltafels

Leerdoelen

  • de console leegmaken

  • werken met wiskundige operatoren

  • interactie met de gebruiker

Functionele analyse

Je schrijft een programma dat de tafel van vermenigvuldiging voor 411 geeft. Dit programma wacht steeds tot de gebruiker op ENTER duwt voor het het volgend resultaat toont. Verder maakt het steeds het scherm leeg voor het een nieuw resultaat toont. Zie "programmaverloop".

Technische analyse

Noem de methode voor deze oefening Maaltafels. Je kent nog geen lusstructuren, dus probeer deze niet te gebruiken, zelfs als je ze al ergens anders bent tegengekomen. Schrijf gewoon tien instructies.

voorbeeldinteractie(s)

1 * 411 is 411.
2 * 411 is 822.

(enzovoort)

10 * 411 is 4110.

Technische hulp

Programmaverloop

Voor elk resultaat wordt het scherm eerst leeggemaakt. Daarna pas wordt het resultaat getoond. Wanneer de gebruiker op ENTER duwt, wordt deze handeling herhaald voor het volgende resultaat (of eindigt het programma, na het tiende resultaat). Het scherm leegmaken doe je met Console.Clear(). Plaats 411 ook in een variabele.

Testscenario's

  • Test uit zoals gegeven.

  • Test uit voor 511. Je zou maar één teken in je code moeten aanpassen als je de instructies hebt gevolgd.

Oefening: H2-ruimte

Leerdoelen

  • werken met kommagetallen

Functionele analyse

Je massa is overal dezelfde en wordt uitgedrukt in kilogram. Je gewicht daarentegen is afhankelijk van de zwaartekracht van de plek waar je bent en wordt uitgedrukt in Newton. Je hebt dus een ander gewicht op andere planeten. Zo is je gewicht veel groter op Jupiter dan op Mars, omdat Jupiter meer zwaartekracht uitoefent dan Mars. Schrijf een programma dat je gewicht op aarde omzet naar je gewicht op een ander hemellichaam. Je krijgt volgende omzettingstabel:

  • Mercurius: 0.38 (een persoon van 100kg voelt zich alsof hij 38kg weegt)

  • Venus: 0.91

  • Aarde: 1.00 (een persoon van 100kg voelt zich alsof hij 100kg weegt)

  • Mars: 0.38

  • Jupiter: 2.34

  • Saturnus: 1.06

  • Uranus: 0.92

  • Neptunus: 1.19

  • Pluto: 0.06

Technische analyse

Noem de methode voor deze oefening Ruimte.

voorbeeldinteractie(s)

Op Mercurius voel je je alsof je 26.22kg weegt.
Op Venus voel je je alsof je 62.79kg weegt.
Op Aarde voel je je alsof je 69kg weegt.
Op Mars voel je je alsof je 26.22kg weegt.
Op Jupiter voel je je alsof je 161.46kg weegt.
Op Saturnus voel je je alsof je 73.14kg weegt.
Op Uranus voel je je alsof je 63.48kg weegt.
Op Neptunus voel je je alsof je 82.11kg weegt.
Op Pluto voel je je alsof je 4.14kg weegt.

Technische hulp

Programmaverloop

Plaats je gewicht in een variabele. Kies zelf een geschikt type.

Testscenario's

  • Test uit voor je eigen gewicht.

  • Test uit voor het gewicht van een persoon met een massa van 100kg.

Kleuren in Console

Kleuren in console

Je kan in console-applicaties zelf bepalen in welke kleur nieuwe tekst op het scherm verschijnt. Je kan zowel de kleur van het lettertype instellen (via ForegroundColor) als de achtergrondkleur (BackgroundColor).

Je kan met de volgende expressies de console-kleur veranderen, bijvoorbeeld de achtergrond in blauw en de letters in groen:

Console.BackgroundColor = ConsoleColor.Blue;
Console.ForegroundColor = ConsoleColor.Green;

Vanaf dan zal alle tekst die je na deze 2 expressies via WriteLine naar het scherm stuurt met deze kleuren werken.

Een voorbeeld:

Console.WriteLine("Tekst in de standaard kleur");
Console.BackgroundColor = ConsoleColor.Blue;
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Deze tekst komt in het groen met blauwe achtergrond");
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("En deze in het rood met blauwe achtergrond");

Als je deze code uitvoert krijg je als resultaat:

Kleur in console gebruiken is nuttig om je gebruikers een minder eentonig en meer informatieve applicatie aan te bieden. Je zou bijvoorbeeld alle foutmeldingen in het rood kunnen laten verschijnen.

Kleur resetten

Soms wil je terug de originele applicatie-kleuren hebben. Je zou manueel dit kunnen instellen, maar wat als de gebruiker slecht ziend is en in z'n besturingssysteem andere kleuren als standaard heeft ingesteld?!

De veiligste manier is daarom de kleuren te resetten door de Console.ResetColor() methode aan te roepen zoals volgend voorbeeld toont:

Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Error!!!! Contacteer de helpdesk");
Console.ResetColor();
Console.WriteLine("Het programma sluit nu af");

Mogelijke kleuren

Alle kleuren die beschikbaar zijn zijn beschreven in ConsoleColor deze zijn:

  • ConsoleColor.Black

  • ConsoleColor.DarkBlue

  • ConsoleColor.DarkGreen

  • ConsoleColor.DarkCyan

  • ConsoleColor.DarkRed

  • ConsoleColor.DarkMagenta

  • ConsoleColor.DarkYellow

  • ConsoleColor.Gray

  • ConsoleColor.DarkGray

  • ConsoleColor.Blue

  • ConsoleColor.Green

  • ConsoleColor.Cyan

  • ConsoleColor.Red

  • ConsoleColor.Magenta

  • ConsoleColor.Yellow

Oefeningen

Structuur van je oefeningen

Om je oefeningen ordelijk bij te houden, gaan we al een aantal zaken gebruiken die je pas verder in de cursus in detail leert. Hieronder krijg je een korte inleiding.

Stap 1: een project LaboOefeningen maken

Volg hiervoor de instructies in het document op DigitAP.

Stap 2: een klasse maken EenProgrammaSchrijvenInCSharp

Dit is nieuw. Klassen zijn een extra organisatie-eenheid van code. Ze spelen een grote rol in objectgeoriënteerd programmeren, maar kunnen ook gebruikt worden om stukken code verder te groeperen. Dit is voorlopig het enige dat wij er mee zullen doen. Via "File" maak je een nieuw bestand aan. Je kiest ervoor een nieuwe C#-klasse te maken met de naam EenProgrammaSchrijvenInCSharp.

Stap 3: een eigen methode maken

Een definitie van een methode bevat één taak die kan worden uitgevoerd. Onze eerste oefenprogramma's zullen telkens één methode zijn. De werking van methodes wordt verder in de cursus in detail omschreven. Soms moet je eerst iets overnemen voor je de functie van elk onderdeeltje kan begrijpen!

Neem aandachtig volgende code over, zodat jouw bestand exact dit bevat:

Belangrijke onderdelen:

  • using System; maakt onderdelen uit de System namespace toegankelijk. Zonder dit kan je geen gebruik maken van Console.

  • namespace LaboOefeningen geeft aan dat alles wat binnen de buitenste accolades (de symbolen op regels 4 en 11) staat behoort tot die namespace. Het is de "achternaam" van jouw code. Als er bijvoorbeeld twee stukken code EenProgrammaSchrijvenInCSharp zijn, kan je ze uit elkaar houden door te zeggen over welke namespace het gaat. Dit is hetzelfde als twee personen die dezelfde voornaam hebben, maar een andere achternaam.

  • public: hier komen we pas een stuk later op terug. Voorlopig zetten we het standaard bij klassen en methoden.

  • class: zie dit voorlopig als een verdere onderverdeling van je code. Eigenlijk zijn klassen veel meer dan dat, maar dat is voor later.

  • static: dit is sowieso nodig wanneer we klassen enkel zien als een verdere onderverdeling van je code. We zetten het bij onze methoden.

  • void: voorlopig zetten we dit altijd bij onze methoden. Het betekent ongeveer: "deze methode voert een commando uit, eerder dan een antwoord te geven op een vraag".

  • MijnEersteMethode: dit is de naam van onze methode. We kiezen deze zelf.

  • (): dit betekent dat de methode geen extra informatie nodig heeft om uit te voeren. Soms zetten we ook zaken tussen deze haakjes, de zogenaamde "parameters". Dit komt later aan bod.

  • Alles tussen de accolades op regel 7 en 9: de "body" van de methode. Dit is eigenlijk wat er gebeurt als we de methode gebruiken.

Stap 4: je eigen methode oproepen

Standaard voert de code in de klasse Program, in de methode Main uit. Dat is het beginpunt van ons programma. We kunnen de body van Main aanpassen. We zullen hierin aangeven dat onze eigen methode moet worden opgeroepen als volgt:

We noemen de code in de body een "oproep" van de methode MijnEersteMethode. Hierin staat niet hoe die methode werkt. Er staat alleen dat we ze willen gebruiken.

Stap 5: opkuisen

Je mag nu zowel de definitie als de oproep van MijnEersteMethode wissen. In de oefeningen die volgen, maak je telkens nieuwe methodes. Test ze telkens uit door de oproep te vervangen. Laat de definitie staan wanneer je overgaat naar de volgende oefening. Later zullen we een keuzemenu maken dat ons toestaat makkelijk een oefening naar keuze te demonstreren.

Oefening: H1-MijnEersteProgramma

Leerdoelen

  • een eigen programma kunnen uitvoeren

  • input en output via Console.ReadLine en Console.WriteLine

Functionele analyse

Binnen een zgn. dos-box wordt een titel weergegeven, nl. dit is mijn eerste c# programma. Vervolgens wordt gevraagd je naam te noteren. Wanneer je je naam hebt genoteerd en op enter hebt gedrukt, verschijnt de tekst “hallo [en je ingegeven naam]”.

Technische analyse

voorbeeldinteractie(s)

Technische hulp

maak een methode met de naam MijnEersteProgramma

Programmaverloop

Wat het lezen en schrijven van tekst betreft moet gebruik gemaakt worden Console.WriteLine en Console.ReadLine.

Testscenario's

  • Probeer meer dan 200 tekens in te voeren

  • Probeer geen tekst in te voeren

Oefening: H1-rommelzin

Leerdoelen

  • een eigen programma kunnen uitvoeren

  • input en output via Console.ReadLine en Console.WriteLine

  • de computer leren zien als "domme verwerker"

Functionele analyse

Dit programma verwerkt tekst die door de gebruiker wordt ingetypt. Het print nieuwe berichten die deze tekst bevatten uit. Het print niet de berichten die je verwacht: het zal de antwoorden door elkaar halen en je favoriete kleur tonen wanneer het beweert je favoriete eten te tonen, enzovoort. De verbanden worden duidelijk uit de voorbeeldinteractie.

Technische analyse

Organisatie van de code

Schrijf dit programma als een methode met de naam Rommelzin binnen de klasse EenProgrammaSchrijvenInCSharp. Test uit door deze methode op te roepen binnen de Main methode.

voorbeeldinteractie(s)

Wat is je favoriete kleur?
> blauw
Wat is je favoriete eten?
> spaghetti
Wat is je favoriete auto?
> Toyota Aygo
Wat is je favoriete film?
> Robocop 2
Wat is je favoriete boek?
> The Gone-Away World
Je favoriete kleur is spaghetti. Je eet graag Toyota Aygo. Je lievelingsfilm is The Gone-Away World en je favoriete boek is Robocop 2.

Technische hulp

Programmaverloop

Per regel die getoond wordt op het scherm, maak je gebruik van Console.WriteLine. Per regel die je zelf intypt, maak je gebruik van Console.ReadLine. Zorg zelf voor de juiste ondersteunende code.

Testscenario's

  • Test uit met een héél lang stuk tekst (meer dan 200 tekens) voor je favoriete kleur.

  • Test uit met tekst met internationale karakters, bijvoorbeeld de ç.

  • Ga na wat er gebeurt als je een lege regel invoert, dus als je meteen op ENTER duwt wanneer gevraagd wordt om invoer.

Oefening: H1-gekleurde-rommelzin

Leerdoelen

  • de kleur van tekst in de console aanpassen

  • herhaling van de leerdoelen uit H1-rommelzin

Functionele analyse

Dit programma werkt net als H1-rommelzin, maar elke regel die aan de gebruiker wordt getoond, krijgt een andere kleur. De namen van de kleuren die je gebruikt (in deze volgorde) zijn:

  1. DarkGreen

  2. DarkRed

  3. DarkYellow

  4. Blue

  5. Cyan

  6. Red

Technische analyse

Organisatie van de code

Schrijf deze oefening als een nieuwe methode met de naam GekleurdeRommelzin in de klasse EenProgrammaSchrijvenInCSharp. Test uit door deze methode op te roepen binnen de Main methode.

voorbeeldinteractie(s)

Technische hulp

Programmaverloop

Voor elke regel die in kleur getoond wordt, wissel je de voorgrondkleur. Op de juiste plaatsen in de code herstel je de oorspronkelijke kleuren van de terminal.

Testscenario's

  • Test opnieuw uit met een kleur, maaltijd, auto, film en boek naar keuze.

Enkelvoudige booleaanse expressies

Relationele operators

Om beslissingen te kunnen nemen in C# moeten we kunnen nagaan of een bepaalde uitspraak waar of niet waar is. Anders gezegd: we moeten stukjes code kunnen schrijven waarvan we achteraf kunnen zeggen of ze "waar" of "niet waar" zijn. Zo'n stukjes code noemen we booleaanse expressies. De meest typische booleaanse expressies schrijf je met de relationele operatoren. Deze stellen vergelijkingen tussen stukjes data voor. Gelukkig ken je deze al uit het lager onderwijs en moet je alleen de notatie hieronder leren:

C#-syntax

Betekenis

>

groter dan

<

kleiner dan

==

gelijk aan

!=

niet gelijk aan

<=

kleiner dan of gelijk aan

>=

groter dan of gelijk aan

"Gelijk aan" noteren we met twee symbolen, omdat één symbool gebruikt wordt voor een toekenning.

Deze operatoren leveren je altijd één van twee mogelijkheden als uitkomst: true of false. Je kan dit ook uittesten: Console.WriteLine(4 < 7); of Console.WriteLine(4 < 2); toont je het verwachte resultaat.

Er bestaan nog simpelere booleaanse expressies dan die met de relationele operatoren: true en false zelf zijn hun eigen resultaat en zijn dus technisch gesproken ook booleaanse expressies. Je mag dus bv. ook true schrijven in plaats van 4 > 2.

Strings samenvoegen

Strings samenvoegen

Je kan strings en variabelen samenvoegen tot een nieuwe string op verschillende manieren. We bekijken volgende twee mogelijkheden:

  • +-operator

  • $ string interpolation

Gebruik zelf stringinterpolatie tenzij het anders gevraagd wordt. Dit is bijna altijd de handigste manier. Online kom je nog (vooral oudere) code tegen die het anders doet, maar we geven deze bewust niet omdat stringinterpolatie bijna altijd het beste werkt.

In dit hoofdstuk

We gaan van volgende informatie uit:

  • Stel dat je 2 variabelen hebt int age=13 en string name="Finkelstein".

  • We willen de inhoud van deze variabelen samenvoegen in een nieuwe string result die zal bestaan uit de tekst:

    Ik ben Finkelstein en ik ben 13 jaar oud.

Volgende 2 manieren tonen hoe je steeds tot voorgaande string zal komen.

Manier 1: String samenvoegen met de +-operator

Als je de + tussen strings plaatst, krijgt deze operator een andere betekenis dan tussen getallen. De strings worden dan achter elkaar geplakt. Als iets geen string is en op deze manier wordt gebruikt, wordt eerst een tekstvoorstelling bepaald, zoals hieronder bij age (want dat is een int).

string result = "Ik ben " + name + " en ik ben " + age + " jaar oud.";

Op het eerste zicht is dit een eenvoudige manier om strings op te bouwen, maar ze heeft een paar belangrijke nadelen:

  • je moet vaak afwisselen tussen aanhalingstekens en plustekens

  • het is lastig spaties en leestekens juist te noteren op deze manier (merk op dat de stukken tekst in het voorbeeld spaties op de zijkanten bevatten)

  • er komt vrij veel extra werk bij kijken als je data in een specifiek formaat wil weergeven, bijvoorbeeld met een specifiek aantal cijfers na de komma

  • als je grote, complexe strings op deze manier opbouwt, kost het erg veel rekentijd

We geven deze manier van werken vooral mee omdat ze in héél veel programmeertalen bestaat en omdat ze simpel is. Ze is niet bijzonder goed.

Manier 2: String interpolation met $

Via stringinterpolatie schrijf je je string min of meer zoals hij er uiteindelijk moet uitzien, maar vervang je de niet-letterlijke delen door geformatteerde waarden. Dit levert een goed leesbaar resultaat.

Door het $-teken VOOR de string te plaatsen geef je aan dat alle delen in de string die tussen accolades staan als code mogen beschouwd worden. Een voorbeeld maakt dit duidelijk:

string result = $"Ik ben {name} en ik ben {age} jaar oud.";

In dit geval zal dus de inhoud van de variabele name tussen de string op de plek waar nu {name} staat geplaatst worden. Idem voor age. Dit mag, zelfs al is age geen string: hetgeen tussen de accolades staat, wordt altijd intern omgezet naar een string voor het in het resultaat wordt geplaatst.

Expressies en operators

Expressies en operators

Een bewerking die een waarde kan opleveren is een expressie. Anders gezegd: een expressie in C# is iets dat je kan toekennen aan een variabele. Bijvoorbeeld 3+2, Console.ReadLine(), of 7. Als je kan zeggen dat een bewerking een duidelijk resultaat heeft, is het waarschijnlijk een expressie.

Expressie-resultaat toewijzen

Meestal zal je expressies schrijven waarin je bewerkingen op en met variabelen uitvoert. Vervolgens zal je het resultaat van die expressie willen bewaren voor verder gebruik in je code.

Voorbeeld van expressie-resultaat toekennen:

int temperatuursVerschil = temperatuurGisteren - temperatuurVandaag;

Hierbij zal de temperatuur uit de rechtse 2 variabelen worden uitgelezen, van elkaar wordt afgetrokken en vervolgens bewaard worden in temperatuursVerschil.

De voorgaande code kan ook langer geschreven worden als:

int tussenResultaat = temperatuurGisteren - temperatuurVandaag;
int temperatuursVerschil = tussenResultaat;

Een ander voorbeeld van een expressie-resultaat toewijzen maar nu met literals (stel dat we temperatuursVerschil reeds hebben gedeclareerd eerder):

temperatuursVerschil = 21 - 25;

Uiteraard mag je ook combinaties van literals en variabelen gebruiken in je expressies:

int breedte = 15;
int hoogte = 20 * breedte;

Operators

Operators in C# zijn de welgekende 'wiskundige bewerkingen' zoals optellen (+), aftrekken (-), vermenigvuldigen (*) en delen (/). Deze volgen de wiskundige regels van volgorde van berekeningen:

  1. Haakjes

  2. Vermenigvuldigen, delen: * (vermenigvuldigen), / (delen)

  3. Optellen en aftrekken: + en -

    (etc.)

Net zoals in de wiskunde kan je in C# met behulp van de haakjes verplichten het deel tussen de haakjes eerst te doen, ongeacht de andere operators en hun volgorde van berekeningen:

3+5*2 => zal 13 als resultaat geven
(3+5)*2 => zal 16 geven

Je kan nu complexe berekeningen doen door literals, operators en variabelen samen te voegen. Bijvoorbeeld om te weten hoe zwaar je je op Mars zou voelen:

double gewichtOpAarde = 80.3;        //kg
double zwaartekrachtAarde = 9.81;    //m/s² 
double zwaartekrachtMars = 3.711;    //m/s²

double  gewichtOpMars = (gewichtOpAarde/zwaartekrachtAarde) * zwaartekrachtMars; //kg
Console.WriteLine("Op Mars voelt het alsof je " + gewichtOpMars + " kg weegt.");

Automatische berekening van datatypes

De types die je in je berekeningen gebruikt bepalen ook het type van het resultaat. Als je bijvoorbeeld twee int variabelen of literals optelt zal het resultaat terug een int geven.

int result = 3+4;

Je kan echter geen kommagetallen aan int toewijzen. Als je dus twee double variabelen deelt is het resultaat terug een double en zal deze lijn een fout geven daar je probeert een double aan een int toe te wijzen:

int otherResult = 3.1/45.2;

Wat als je een int door een int deelt? Het resultaat is terug een int. Je bent gewoon alle informatie na de komma kwijt. Kijk maar:

int getal1 = 9;
int getal2 = 2;
int result = getal1 / getal2;
Console.WriteLine(result);

Er zal 4 op het scherm verschijnen! (niet 4.5 daar dat geen int is).

Wat als je datatypes mengt? Als je een berekening doet met een int en een double dan zal C# het meest algemene datatype kiezen. In dit geval een double. Volgende code zal dus werken:

double result = 3/5.6;

Volgende niet:

int result = 3/5.6;

Wil je dus het probleem oplossen om 9 te delen door 2 dan zal je minstens 1 van de 2 literals of variabelen door een double moeten omzetten. Het voorbeeld van hierboven herschrijven we dan naar:

int getal1 = 9;
double getal2 = 2.0;
double result = getal1/getal2;
Console.WriteLine(result);

En nu krijgen we wel 4.5.

En complexer?

Het kan subtiel worden in grotere berekeningen.

Stel dat ik afspreek dat je van mij de helft van m'n salaris krijgt. Ik verdien (fictief) 10000 euro per maand. Ik gebruik volgende formule:

double helft = 10000.0 * (1/2);

Hoeveel krijg je van me? 0.0 euro!

De volgorde van berekeningen zal eerst het gedeelte tussen de haakjes doen: 1 delen door 2 geeft 0, daar we een int door een int delen en dus terug een int als resultaat krijgen. Vervolgens zullen we deze 0 vermenigvuldigen met 10000.0 . We vermenigvuldigen weliswaar een double (het salaris) met een int, maar die int is reeds 0 en we krijgen dus 0.0 als resultaat.

Wil je het dus eerlijk spelen dan zal je de formule moeten aanpassen naar:

double helft = 10000.0 * (1.0/2); // of 1/2.0 of zonder haakjes

Nu krijgt het gedeelte tussen de haakjes een double als resultaat, namelijk 0.5 dat we dan kunnen vermenigvuldigen met het salaris om 5000.0 te krijgen.

Datatypes

Een essentieel onderdeel van C# is kennis van datatypes. Een datatype is, zoals de naam het zegt, een soort waartoe bepaalde gegevens kunnen behoren. Wanneer je data wenst te bewaren in je applicatie dan zal je je moeten afvragen wat voor soort data het is. Gaat het om een getal, een geheel getal, een kommagetal, een stuk tekst of misschien een binaire reeks? Een variabele met een bepaald datatype kan een bepaald soort data bewaren en dit zal afhankelijk hiervan een bepaalde hoeveelheid computergeheugen vereisen.

Er zijn tal basistypes in C# gedeclareerd (zogenaamde built-in datatypes). Dit semester leren we werken met datatypes voor:

  • Gehele getallen: sbyte, byte, short, ushort, int, uint, long

  • Kommagetallen: double, float, decimal

  • Tekst: char, string

  • Booleans: bool

Het datatype string heb je al in actie gezien in het vorig hoofdstuk. Je hebt toen al een variabele aangemaakt van het type string door de zin string result;.

Verderop koppelden we de naam result dan aan het resultaat van een actie, namelijk inlezen van tekst via Console.ReadLine (eerst wordt dat resultaat uitgerekend, dan pas wordt het aan de naam gekoppeld):

result = Console.ReadLine();

Basistypen voor getallen

C# heeft een hoop datatypes gedefinieerd om te werken met getallen zoals wij ze kennen, gehele en kommagetallen. Intern zullen deze getallen steeds binair bewaard worden, maar dat merken we zelden tijdens het programmeren.

De basistypen van C# om getallen in op te slaan zijn:

  • Voor gehele getallen: sbyte, byte, short, ushort, int, uint, long

  • Voor kommagetallen: double, float, decimal

Deze datatypes hebben allemaal een bepaald bereik, wat een rechtstreeks gevolg is van de hoeveelheid geheugen die ze innemen.

Gehele getallen

Voor de gehele getallen:

Type

Geheugen

Bereik

Meer info

sbyte

8 bits

-128 tot 127

byte

8 bits

0 tot 255

short

16 bits

-32768 tot 32767

ushort

16 bits

0 tot 65535

int

32 bits

-2 147 483 648 tot 2 147 483 647

uint

32 bits

0 tot 4294967295

long

64 bits

-9 223 372 036 854 775 808 tot 9 223 372 036 854 775 807

ulong

64 bits

0 tot 18 446 744 073 709 551 615

char

16 bits

0 tot 65535

Enkele opmerkingen bij deze tabel:

  • De s vooraan sbyte types staat voor signed: m.a.w. 1 bit wordt gebruikt om het + of - teken te bewaren.

  • De u vooraan ushort, uint en ulong staat voor unsigned. Het omgekeerde van signed dus. Kwestie van het ingewikkeld te maken. Deze twee datatypes hebben dus geen teken en zijn altijd positief.

  • char heeft dezelfde kenmerken als ushort, maar dient voor iets anders. ushort gebruik je echt om getallen binnen dat bereik voor te stellen. char bewaart karakters, d.w.z. symbolen zoals letters. Een beetje vereenvoudigd kan je stellen dat er een tabel is waarin elk symbool gekoppeld is aan een getal, zodat een reeks nulletjes en eentjes ook een letter kan voorstellen.

Kommagetallen

Voor de kommagetallen zijn er maar 3 mogelijkeden. Ieder datatype heeft een voordeel tegenover de 2 andere, dit voordeel staat vet in de tabel:

Type

Geheugen

Bereik

Precisie

float

32 bits

6 to 9 digits

double

64 bits

15 to 17 digits

decimal

128 bits

28 to 29 digits

Zoals je ziet moet je bij kommagetallen een afweging maken tussen 3 even belangrijke criteria. Heb je zeer grote precisie (veel cijfers na de komma) nodig, dan ga je voor een decimal. Wil je vooral erg grote of erg kleine getallen (met meer kans op afrondingen), dan kies je voor double.

Bij twijfel opteren we meestal voor kommagetallen om het double datatype te gebruiken. Bij gehele getallen kiezen we meestal voor int.

Boolean datatype

Het bool (boolean) is het eenvoudigste datatype van C#. Het kan maar 2 mogelijke waarden bevatten: true of false. 1 of 0 met andere woorden. In het Nederlands meestal uitgedrukt als "waar" en "niet waar".

Tekst/String datatype

We besteden verderop een heel apart hoofdstuk aan tonen hoe je tekst en enkele karakters kan bewaren in variabelen. De basis:

  • Tekst kan bewaard worden in het string datatype

    • Letterlijke tekst schrijf je tussen dubbele quotes, bijvoorbeeld "Hallo Wereld!"

  • Een enkel karakter wordt bewaard in het char datatype, dat we ook hierboven al even hebben zien passeren.

    • Letterlijke karakter schrijf je tussen enkele quotes, bijvoorbeeld 'à'

Samengestelde booleaanse expressies

Logische operatoren

De logische EN, OF en NIET-operatoren die je misschien kent uit de lessen technologie of elektronica kan je ook gebruiken in C#:

Je kan de &&-operator tussen twee booleaanse expressies zetten om een nieuwe, grote expressie te schrijven die afhangt van de kleinere expressies aan beide kanten. Idem voor de ||-operator. Je kan de niet-operator voor een booleaanse expressie zetten om het resultaat hiervan om te draaien. Bijvoorbeeld:

Hieronder krijg je nog eens het overzicht van de werking van de AND en OR operatoren:

Test jezelf

Wat zal de uitkomst zijn van volgende expressies? (tip: het zal steeds true of false zijn, niets anders)

  • 3>2

  • 4!=4

  • 4<5 && 4<3

  • "a"=="a" || 4>=3

  • (3==3 && 2<1) || 5!=4

  • !(4<=3)

  • true || false

  • !true && false

Booleaanse expressies algemeen

Je bent niet beperkt tot het gebruik van expressies met één relationele operator in de voorwaarde van een if. Je mag om het even welk type booleaanse expressie gebruiken. Anders gezegd: als het gedeelte tussen haakjes uiteindelijk maar true of false oplevert, is het goed. We kunnen dus ook gebruik maken van de logische operatoren &&, ||, ! in onze voorwaarden.

Goed gebruik van booleaanse expressies kan onze programma's drastisch vereenvoudigen. We tonen dit met een voorbeeld.

Volgend programma bepaalt of je korting krijgt op het openbaar vervoer. Volg zelf de flowchart om de logica te snappen:

Maar we kunnen dit eenvoudiger zo schrijven:

Deze code zegt dat je korting krijgt als je jonger bent dan 18 of als je ouder bent dan 65. Ze doet hetzelfde als de eerste versie, maar het diagram is eenvoudiger en de gegenereerde code is dat ook. Ook via de andere operatoren && en ! kan je gelijkaardige vereenvoudigingen verkrijgen.

If, else, else if

In dit deel zullen we bekijken hoe we ons programma dynamischer kunnen maken met behulp van het if-statement, al dan niet uitgebreid met else en else if.

If

De if is een van de elementairste constructies in een programmeertaal. Vrijwel elke programmeertaal bezit deze constructie. We zullen de werking ervan eerst wat algemener bekijken, zodat je het concept goed begrijpt. Daarna zullen we inzoomen op de syntax in C#.

Het basisidee is als volgt: een if-constructie bevat code die enkel uitvoert als een booleaanse expressie true oplevert. Met andere woorden: als een voorwaarde naar keuze waar is. We noemen dergelijke code conditionele code, want een conditie is een voorwaarde.

We zullen dit tonen met een Flowgorithm programma. Dit is een beperkte, maar heel visuele programmeertaal. In een Flowgorithm programma start je bij Main en volg je steeds de pijlen.

De gele box stelt een declaratie voor. Ook in Flowgorithm moet je variabelen declareren. Het blauw parallellogram: de gebruiker geeft iets in (de waarde van wachtwoord). De rode ruit bevat een booleaanse expressie. Dan is er een vertakking, met daarop de waarden True en False. We volgen de pijl met daarop de uitkomst van de booleaanse expressie. Als we dus het juiste wachtwoord intypen, krijgen we de geheime info, anders gebeurt er niets.

Het if-statement stemt overeen met alles tussen de rode ruit en het rode bolletje. Dus als aan een bepaalde voorwaarde voldaan is, voeren we afgebakende code uit.

Uit dit programma kan je dan ook volgende C#-code afleiden:

In de gegenereerde code stemt de rode ruit dus overeen met de haakjes meteen na if en stemt de tak True overeen met de accolades.

Flowgorithm is vrij te downloaden, dus als je moeite hebt met deze concepten, wordt aangeraden hier wat mee te experimenteren.

Veelgemaakte if-fouten

Er zijn enkele veelgemaakte fouten waar je op moet letten:

Appelen en peren vergelijken

De types in je booleanse expressie moeten steeds vergelijkbaar zijn. Volgende code is dus fout: if( "4" > 3) daar we hier een string met een int vergelijken.

Accolades vergeten

Als je geen accolades schrijft, verandert de werking van if. Het gevolg zal zijn dat enkel het eerste statement na de if zal uitgevoerd worden indien true. Gebruiken we de if van daarnet maar zonder accolades dan zal het tweede statement altijd uitgevoerd worden ongeacht de if:

Voor ons is het simpel: we schrijven if altijd met accolades.

If/else

Het eerdere voorbeeld toont dat we soms actie willen ondernemen als aan een voorwaarde voldaan is, maar heel vaak willen we een andere actie ondernemen als aan diezelfde voorwaarde niet voldaan is. In dat geval maken we gebruik van de if ... else ....

We gaan terug naar onze login. Een fout wachtwoord ingeven heeft bepaalde gevolgen. Dit kan bijvoorbeeld een alarm doen afgaan, omdat indringers soms foute wachtwoorden ingeven tot ze toevallig het juiste vinden. We stellen dit hier voor door een actie toe te voegen als het wachtwoord niet klopt.

De overeenkomstige C# code:

In bovenstaande code stemt de rode ruit dus nog steeds overeen met de haakjes meteen na if, stemt de tak True overeen met de accolades vlak na de ronde haakjes en stemt else { ... } overeen met de tak False. Anders gezegd: als aan een bepaalde voorwaarde voldaan is, voeren we het eerste blok afgebakende code uit, anders voeren we het tweede blok afgebakende code uit.

Code voor beide gevallen

Als we iets altijd willen doen, hoort dat niet in een vertakking. Dan zetten we het in de flowchart na het rode bolletje. In ons programma zetten we het dan na de volledige if ... else ...:

De gegenereerde code is dan:

Nesting

Het is perfect mogelijk om bepaalde controles enkel te doen als eerdere controles wel of niet gelukt zijn. Dit kan er zo uitzien:

In de gegenereerde code leidt dit tot geneste conditionele code: een if binnenin een grotere if of else. In digt geval gaat het om een if in een else. Je mag zo diep nesten als je maar wil. Er is geen technische limiet, maar je code zal wel onleesbaar worden als je overdrijft.

else if

Nesten van conditionele code levert soms code op die moeilijk te ontrafelen is. Het gebeurt vaak dat we een else nodig hebben met meteen daarin terug een if, om zo verschillende gevallen af te handelen.

We kunnen een programma maken dat dit demonstreert: Er is niet aan de hoofdvoorwaarde voldaan, maar er is wel aan een andere voorwaarde voldaan:

De gegenereerde code voor dit frament is technisch juist:

Maar Flowgorithm is geen menselijke programmeur. Een menselijke programmeur zou volgende voorstelling gebruiken, die hetzelfde doet, maar makkelijker leesbaar is:

Oefeningen

Al deze oefeningen maak je in een klasse Beslissingen

Oefening: H4-Schoenenverkoper

Leerdoelen

  • flowchart omzetten naar conditionele code

  • Eenvoudige conditie

  • Uitbreiding: samengestelde booleaanse expressie

Functionele analyse

Basis: Maak een programma dat aan de gebruiker vraagt hoeveel paar schoenen hij/zij wenst te kopen. Ieder paar schoenen kost normaal 50 euro. Indien de gebruiker 2 paar of meer koopt, is er 10% korting. Toon aan de gebruiker de totale prijs.

Uitbreiding: Breid in een tweede stap je programma uit zodat de korting enkel geldt als de gebruiker een klantenkaart heeft.

Technische analyse

Maak een methode met de naam SchoenenVerkoper. Maak gebruik van een if. Zet volgende flowchart om in code. Een Real in Flowgorithm stemt overeen met een double in C#.

Basis:

Uitbreiding:

Voorbeeldinteractie(s)

Basis:

Uitbreiding:

Oefening: H4-EvenOneven

Leerdoelen

  • flowchart omzetten naar conditionele code

  • tweevoudige conditie

  • gebruik modulo

Functionele analyse

Maak een programma dat aan de gebruiker een geheel getal vraagt. Het programma geeft terug of het ingegeven getal even of oneven is.

Technische analyse

Maak een methode met de naam EvenOneven.

Een getal is even als de rest van de deling door 2 nul is. Hiervoor kan je de modulo operator gebruiken. Het teken voor de modulo is het % -teken. Voorbeelden: 7%2 geeft 1 => 7 is oneven 8%2 geeft 0 => 8 is even

Zet volgende flowchart om in code:

Voorbeeldinteractie(s)

Oefening: H4-PositiefNegatiefNul

Leerdoelen

  • flowchart omzetten naar conditionele code

  • meervoudige conditie

Functionele analyse

Maak een programma dat aan de gebruiker een geheel getal vraagt. Het programma geeft terug of het ingegeven getal positief, negatief of 0 is.

Technische analyse

Maak een methode met de naam PositiefNegatiefNul.

Maak gebruik van een if – else if - else.

Zet volgende flowchart om in code:

Voorbeeldinteractie(s)

Oefening: H4-BMIBerekenaar

Leerdoelen

  • meervoudige conditie

  • gebruik van else if

Functionele analyse

De formule is: BMI = gewicht / lengte².

Je toont niet enkel de BMI maar ook een beoordeling over de BMI:

- BMI lager dan 18,5 => ondergewicht

- BMI vanaf 18,5 maar lager dan 25 => normaal gewicht

- BMI vanaf 25 maar lager dan 30 => overgewicht

- BMI vanaf 30 maar lager dan 40 => zwaarlijvig

- BMI hoger dan 40 => ernstige obesitas

Technische analyse

Maak een methode met de naam BMIBerekenaar.

Maak gebruik van een if – else if – else if …

Voorbeeldinteractie

Oefening: H4-GrootsteVanDrie

Leerdoelen

  • meervoudige conditie

  • samengestelde booleaanse expressie

  • gebruik van else if

Functionele analyse

Maak een programma om van 3 ingegeven getallen, het grootste te bepalen.

Technische analyse

Maak een methode met de naam GrootsteVanDrie.

Maak gebruik van een if – else if - else

Voorbeeldinteractie

Oefening: H4-Examens

Leerdoelen

  • conditie

  • samengestelde booleaanse expressie

Functionele analyse

Maak een programma waarmee je aan de gebruiker het resultaat van 3 examens opvraagt. De opgevraagde resultaten zijn de behaalde punten op 100. Om te slagen moet de student een gemiddelde van meer dan 50% hebben én maximaal 1 onvoldoende.

Technische analyse

Maak een methode met de naam Examens.

Maak gebruik van een if – else.

Voorbeeldinteractie

Oefening: H4-Wet van Ohm

Leerdoelen

- conditionele berekeningen

Functionele analyse

De Wet van Ohm houdt in dat een elektrische stroom (voorgesteld als I) gelijk is aan een spanningsverschil (U) gedeeld door een weerstand (R), dus I = U / R.

Vraag aan de gebruiker wat hij wenst te berekenen: Spanning, Weerstand of Stroomsterkte. Vraag vervolgens de twee andere waarden. Als dus de gebruiker "Spanning" kiest, vraag je aan de gebruiker de waarden van de stroomsterkte en de weerstand. Bereken m.b.v. de Wet van Ohm de gewenste waarde en toon aan de gebruiker.

Technische analyse

Maak een nieuwe methode genaamd WetVanOhm.

Denk eraan dat de gegeven formule wiskundig gedefinieerd is. Houd rekening met het feit dat deze drie maten uitgedrukt kunnen worden in kommagetallen.

Voorbeeldinteractie(s)

Keuzemenu's maken

Leerdoelen:

- Conditionele functionaliteit

Functionele analyse:

We willen dat de gebruiker een menu ter beschikking heeft om eerst te kiezen uit welk hoofdstuk er een oefening moet gekozen worden en vervolgens welke oefening er moet uitgevoerd worden.

Technische analyse

Maak in je klasse Program een hoofdmenu. Deze methode laat de gebruiker kiezen uit één van de hoofdstukken. Na de keuze wordt het scherm leeggemaakt en dan wordt de methode Keuzemenu van de juiste klasse opgeroepen. Elke klasse die je tot hiertoe in dit project gemaakt hebt, voorzie je dus van een methode Keuzemenu, zodat de gebruiker kan kiezen uit de oefeningen (=methodes) binnen het gekozen hoofdstuk.

Voorbeeldinteractie

Scope van variabelen

Scope van variabelen

De locatie waar je een variabele aanmaakt bepaalt de scope, oftewel de gegarandeerde levensduur, van de naam van de variabele. Ruwweg: als je binnen hetzelfde stel accolades werkt waarin een variabele is gedeclareerd, dan is die naam in scope en kan je hem gebruiken. Indien je de variabele dus buiten die accolades nodig hebt, dan heb je een probleem: de variabele is enkel bereikbaar binnen de accolades vanaf het punt in de code waarin het werd aangemaakt.

Zeker wanneer je begint met if, loops, methoden, etc. zal de scope belangrijk zijn: deze code-constructies gebruiken steeds accolades om codeblocks aan te tonen. Een variabele die je dus binnen een if-blok aanmaakt zal enkel binnen dit blok bestaan, niet erbuiten.

Wil je dus getal ook nog buiten de if gebruiken zal je je code moeten herschrijven zodat getal voor de if wordt aangemaakt:

Variabelen met zelfde naam

Zolang je in de scope van een variabele bent kan je geen nieuwe variabele met dezelfde naam aanmaken:

Volgende code is dus niet toegestaan:

Je krijgt de error: A local variable named 'getal' cannot be declared in this scope because it would give a different meaning to 'getal', which is already used in a 'parent or current' scope to denote something else

Enkel de tweede variabele een andere naam geven is toegestaan in het voorgaande geval.

Dit is wel geldig, daar de scope van de eerste variabele afgesloten wordt door de accolades:

Beslissingen intro

Nu we de elementaire zaken van C# en Visual Studio kennen is het tijd om onze programma's wat interessanter te maken. De ontwikkelde programma's tot nog toe waren steevast lineair van opbouw, ze werden lijn per lijn uitgevoerd zonder de mogelijkheid om de flow van het programma aan te passen. Het programma doorliep de lijnen braaf na elkaar en wanneer deze aan het einde kwam sloot het programma zich af.

Onze programma's waren met andere woorden niet meer dan een eenvoudige oplijstingen van opdrachten. Je kan het vergelijken met een lijst die je vertelt over hoe je een brood moet kopen:

  1. Neem geld uit spaarpot

  2. Wandel naar de bakker om de hoek

  3. Vraag om een brood

  4. Krijg het brood

  5. Betaal het geld aan de bakker

  6. Keer huiswaarts

  7. Smullen maar

Alhoewel dit algoritme redelijk duidelijk en goed zal werken, zal de realiteit echter zelden zo rechtlijnig zijn. Een beter algoritme (dat foutgevoeliger is én interactiever voor de eindgebruiker) zal afhankelijk van de omstandigheden (bakker gesloten, geen geld meer, etc.) mogelijke andere stappen ondernemen. Het programma zal beslissingen nemen op basis van aanwezige informatie.

In een typisch project, bijvoorbeeld een bestelsysteem, kunnen we beslissingen gebruiken voor tal van zaken:

  • als we de voorraad van producten gaan bijhouden en we willen reageren wanneer de voorraad op is

  • als we op bepaalde dagen van de week kortingen op bepaalde producten willen geven in plaats van de gebruiker deze zelf te laten kiezen

  • als we spaarpunten willen introduceren en de gebruiker deze kan inwisselen

Omzetten van en naar strings

Input van de gebruiker verwerken

Conversie

Zoals eerder aangegeven, kan je geen strings toekennen aan variabelen van type int of omgekeerd. Toch wil je soms een getal beschouwen als tekst, of tekst omzetten naar een getal .NET heeft hiervoor methoden die je kunnen helpen om data van het ene type naar het andere te brengen. Deze methoden zitten binnen de Convert-klasse.

Het gebruik hiervan is zeer eenvoudig. Enkele voorbeelden:

Foutloze input

Voorgaande code veronderstelt dat de gebruiker géén fouten invoert. De conversie zal namelijk mislukken indien de gebruiker bijvoorbeeld IKWEEG10KG invoert in plaats van 10,3.

In het begin van de leercurve moet je er altijd van uitgaan dat de gebruiker foutloze input geeft. Later leer je wel hoe je dit kan afhandelen.

Opgelet: de invoer van kommagetallen door de gebruiker is afhankelijk van de landinstellingen van je besturingssysteem. Staat deze in Belgisch/Nederlands dan moet je kommagetallen met een KOMMA(,) invoeren (dus 9,81), staat deze in het Engels dan moet je een PUNT(.) gebruiken (9.81).

Opgelet 2: In je C# code moet je doubles ALTIJD met een punt schrijven. Dit is onafhankelijk van je taalinstellingen.

Oefeningen

Al deze oefeningen maak je in een klasse StringsEnHunMethoden

Oefening: VariabelenEnHoofdletters

Leerdoelen

  • gebruik van variabelen om input en output op te slaan en te tonen

  • functionaliteit van strings

Functionele analyse

Een applicatie vraagt je tekst in te voeren die dan daarna zal worden getoond met allemaal hoofdletters.

Technische analyse

Noem de methode voor deze oefening VariabelenEnHoofdletters.

voorbeeldinteractie(s)

Technische hulp

Programmaverloop

Lees de gebruikersinvoer van de console en sla deze op in een variabele. Zet de inhoud van deze variabele om in hoofdletters. Je kan dit doen door ToUpper() te gebruiken. Uiteindelijk geef je dan het resultaat weer in de console.

Testscenario's

  • Voer tekst in met spaties

  • Voer tekst in van meer dan 100 karakters

  • Voer tekst in van 1 karakter

  • Voer geen tekst in

Oefening: H3-string-interpolation

Leerdoelen

  • gebruik van string interpolation

Functionele analyse

Zelfde als oefeningen maaltafels en ruimte vorig hoofdstuk.

Technische analyse

Je moet twee methoden schrijven. Noem de eerste MaaltafelsStringInterpolatie en de tweede RuimteStringInterpolatie. Deze doen net hetzelfde als hun tegenhangers uit het vorige hoofdstuk, maar je bouwt de getoonde tekst op met stringinterpolatie in plaats van via + en/of Console.Write.

voorbeeldinteractie(s)

Zie oefening H2-maaltafels en H2-ruimte.

Technische hulp

Programmaverloop

Pas string interpolatie m.b.v. $ toe om de veranderlijke onderdelen van de output in te vullen.

Testscenario's

  • Zie oefening H2-maaltafels en H2-ruimte.

Oefening: H3-bereken-btw

Leerdoelen

  • gebruik van string interpolation

Functionele analyse

Een programma vraagt een bedrag en vervolgens btw percentage in te geven waarna het bedrag incl. btw-percentage wordt weergegeven.

Technische analyse

Noem de methode voor deze oefening BerekenBtw.

voorbeeldinteractie(s)

Technische hulp

Programmaverloop

Het bedrag dat wordt ingevoerd moet geconverteerd worden naar een int met Convert.ToInt32.

Pas string interpolatie toe om de output te tonen.

Testscenario's

  • Typ tekst in

  • Geef een veel te groot bedrag in

Oefening: H3-leetspeak

Leerdoelen

  • functionaliteit van strings leren kennen

Functionele analyse

We willen tekst omvormen naar een ander formaat. Laat de gebruiker een lijn tekst ingeven en haal er alle tussenliggende spaties uit en vervang de a's door @

Technische analyse

Gebruik Console.ReadLine om tekst in te lezen en hou bij in een variabele. Pas de nodige string methodes toe om het resultaat te verkrijgen. Noem je methode voor dit programma LeetSpeak.

Programmaverloop

Testscenario's

  • test met een zin zonder a's

  • test met een zin met vijf a's of meer

  • test met een lege string

Oefening: H3-instructies

Leerdoelen

  • leren werken met stringinterpolatie

  • leren werken met methodes van strings

Functionele analyse

We willen met behulp van een programma instructies genereren voor de gebruiker. Meerbepaald wordt automatisch aangegeven in welke map de gebruiker bepaalde bestanden op een UNIX-achtig systeem moet bijhouden.

Voor deze oefening is het verplicht gebruik te maken van een (geïnterpoleerde) string.

Technische analyse

Op basis van de voornaam van de student en de naam van de cursus wordt de map gegeven die de student moet aanmaken (/home/, eerste 3 letters voornaam, in hoofdletters met submap de naam van de cursus. Noem je methode Instructies.

Programmaverloop

Oefening: H3-lotto

Leerdoelen

  • functionaliteit van strings

  • stringinterpolatie

Functionele analyse

De gebruiker voert zijn lottocijfers in. We willen deze op een overzichtelijke manier weergeven.

Technische analyse

Laat de lottocijfers allemaal achter elkaar ingeven, gescheiden door komma's, zonder spaties. De gebruiker wordt verondersteld cijfers onder de 10 in te geven voorafgegaan door een nul. Gebruik de juiste methode om de cijfers uit te string te "knippen" en gebruik het karakter | om de uitvoer te scheiden. Noem je methode Lotto.

Voorbeeldinteractie

Oefening: H3-som-van-cijfers

Leerdoelen

  • functionaliteit van strings

  • stringinterpolatie

Functionele analyse

De gebruiker voert een getal in. Het programma berekent de som van de cijfers in de decimale voorstelling van dit getal.

Technische analyse

We veronderstellen dat de gebruiker een getal van exact vijf cijfers ingeeft, desnoods vooraan opgevuld met nullen. Noem je methode SomVanCijfers.

Voorbeeldinteractie

Onderstaand voorbeeld komt uit op 27, want 6 + 3 + 9 + 2 + 7 is 27.

Oefening: H3-naam-uit-mail

Leerdoelen

  • functionaliteit van strings

  • stringinterpolatie

Functionele analyse

De gebruiker voert een e-mailadres in. Jouw programma toont hieruit het gedeelte dat de naam voorstelt, in hoofdletters.

Technische analyse

We veronderstellen dat de gebruiker een juist mailadres invult. Noem je methode NaamUitEmail.

Voorbeeldinteractie

Oefening: H3-eerste-letter-en-achternaam

Leerdoelen

  • functionaliteit van strings

  • stringinterpolatie

Functionele analyse

De gebruiker voert zijn naam in. Je programma toont dan de eerste letter van de voornaam en de familienaam.

Technische analyse

We veronderstellen dat de gebruiker een voornaam zonder spaties invult. Noem je methode EersteLetterEnAchternaam.

Voorbeeldinteractie

Oefening: H3-toegangscode

Leerdoelen

  • functionaliteit van strings

  • stringinterpolatie

  • omzetting tussen tekst en getal

Functionele analyse

De gebruiker voert enkele persoonlijke gegevens in en op basis hiervan wordt een persoonlijke toegangscode gegenereerd.

Technische analyse

Noem je methode Toegangscode. De code van vier tot vijf cijfers wordt als volgt bepaald:

  • het eerste symbool is de voorlaatste letter van de naam, in kleine letters

  • het tweede symbool is de laatste letter van de naam, in hoofdletters

  • het derde symbool is het laatste cijfer van het geboortejaar

  • het vierde (en eventueel vijfde) symbool is het eerste cijfer van de postcode, in het kwadraat (dus vermenigvuldigd met zichzelf)

Voorbeeldinteractie

orde in .NET
Cheat sheet preview

vind je een tabel terug die uitlegt welke operaties voorrang hebben.

Resultaat voorgaande code
Bron afbeelding : https://www.c-sharpcorner.com/article/change-console-foreground-and-background-color-in-c-sharp/
MijnEersteProgramma runt in de console
De eerste regel behoort niet tot het programma. De rest moet er bij jou hetzelfde uitzien.

We zullen het bool datatype erg veel nodig hebben wanneer we met zullen werken, specifiek de die afhankelijk van de uitslag van een bool bepaalde code wel of niet zullen doen uitvoeren.

Meer info vind je later in .

Maak een programma om de BMI van de gebruiker te berekenen. () De BMI wordt berekend aan de hand van de lengte en het gewicht van de persoon.

Je plaatst bij gebruik van Convert tussen de ronde haakjes de variabele of literal die je wenst te converteren naar een ander type. Merk op dat naar een int converteren met .ToInt32() moet gebeuren. Voor andere types zijn er overeenkomstige methoden. Je kan . Volgende conversies zullen je al ver vooruit helpen:

±1.5⋅10−45 to ±3.4⋅1038±1.5 \cdot 10^{-45} \space to\space ±3.4 \cdot 10^{38}±1.5⋅10−45 to ±3.4⋅1038
±5.0⋅10−324 to ±1.7⋅10308\bold{±5.0 \cdot 10^{-324} \space to\space ±1.7 \cdot 10^{308}}±5.0⋅10−324 to ±1.7⋅10308
±1.0⋅10−28 to ±7.9228⋅1028±1.0 \cdot 10^{-28}\space to\space ±7.9228 \cdot 10^{28}±1.0⋅10−28 to ±7.9228⋅1028
Kennisclip voor deze inhoud
Hier
Kennisclip voor deze inhoud
Kennisclip
Kennisclip voor deze inhoud
Kennisclip voor deze inhoud
Kennisclip voor deze inhoud
beslissingen
if statements
dit hoofdstuk

C#-syntax

Betekenis

&&

en-operator

||

of-operator

!

niet-operator

!(0==2)  //zal true geven

AND

niet waar

waar

niet waar

niet waar

niet waar

waar

niet waar

waar

OR

niet waar

waar

niet waar

niet waar

waar

waar

waar

waar

if (wachtwoord == "gEhEiM")
    Console.WriteLine ("Geheime informatie: ...");
    Console.WriteLine ("Fijne dag nog!");
public static void Main(string[] args) {
  string wachtwoord;
  wachtwoord = Console.ReadLine();
  if (wachtwoord == "gEhEiM") {
    Console.WriteLine("Geheime informatie: ...");
  }
  else if (wachtwoord == "geheim") {
    Console.WriteLine("Warm!");
  }
  else {
    Console.WriteLine("Koud!");
  }
  Console.WriteLine("Fijne dag nog!");
}
if( something == true)
{
    int getal = 0 ;  //Start scope getal
    getal = 6;
} // einde scope getal

Console.WriteLine(getal); // zal niet werken daar de scope van getal al gedaan was
int getal = 0 ;  //Start scope getal
if( something == true)
{
    getal = 6;
} 

Console.WriteLine(getal);
{
    int getal = 0;
    {
        int getal = 5; //Deze lijn is niet toegestaan
    }

}
{
    int getal = 0 ;
    //....
}
//Verder in code
{
    int getal = 5;
}
int userAge = Convert.ToInt32("19"); // string to int
string ageAsText = Convert.ToString(19); // int to string

gewenst type

methode

int

ToInt32

double

ToDouble

string

ToString

boolean

ToBoolean

byte

ToByte

Welke tekst moet ik omzetten?
> Hello World
HELLO WORLD
Geef je tekst in
> Oefening baart kunst!
Oefeningb@@rtkunst!
Wat is je naam?
> Vincent
Wat is de naam van de cursus?
> Programmeren
Maak een map als volgt: /home/VIN/Programmeren
Wat zijn je cijfers (tussen 01 en 45)?
> 05,08,13,18,27,44
Je cijfers zijn:
05|08|13
18|27|44
Gelieve een getal in te voeren dat bestaat uit exact 5 decimale cijfers.
> 63927
De som is 27.
Geef je e-mailadres:
> ann.debrabandere@ap.be
Je naam uit je e-mail is: ANN.DEBRABANDERE
Geef je naam:
> Ann De Brabandere
De eerste letter van je naam is: A.
Je achternaam is: De Brabandere
Geef je naam: > Janssens
Geef je geboortejaar: > 2001
Geef je postcode: > 2000
Je toegangscode is nS14

Loops intro

Herhalingen (loops) creëer je wanneer bepaalde code een aantal keer moet herhaald worden. Hoe vaak de herhaling moet duren is afhankelijk van de conditie die je hebt bepaald. Deze conditie is een booleaanse expressie, net zoals de conditie van if.

In het vorige hoofdstuk leerden we hoe we met behulp van beslissingen onze code konden branchen, aftakken zodat andere code werd uitgevoerd afhankelijk van de staat van bepaalde variabelen of invoer van de gebruiker. Wat we nog niet konden was terug naar boven vertakken. Soms willen we dat een heel stuk code 2 of meerdere keren moet uitgevoerd worden zo lang als of totdat aan een bepaalde conditie wordt voldaan. "Voer volgende code uit tot dat de gebruiker een bepaalde waarde invoert." Dit zal bijna net hetzelfde werken als bij if, maar nadat je code is uitgevoerd, wordt de conditie (opnieuw) gecontroleerd om na te gaan of de code (opnieuw) moet worden uitgevoerd.

Door herhalende code met loops te schrijven maken we onze code korter en bijgevolg ook minder foutgevoelig en beter onderhoudbaar.

Van zodra je dezelfde lijn(en) code onder elkaar in je code ziet staan (door bijvoorbeeld te copy pasten) is de kans zéér groot dat je dit korter kunt schrijven met loops.

Soorten loops

Er zijn verschillende soorten loops:

  • Definite of counted loop: een loop waar het aantal iteraties vooraf van gekend is. (Bijvoorbeeld 100 keer of een aantal keer gelijk aan de waarde van de variabele x).

  • Indefinite of sentinel loop: een loop waarvan op voorhand niet kan gezegd worden hoe vaak deze zal uitgevoerd worden. Input van de gebruiker of een interne test zal bepalen wanneer de loop stopt (bv. "Voer getallen in, voer -1 in om te stoppen")

  • Oneindige loop: een loop die nooit stopt. Soms gewenst, vaak een bug.

Loops in C#

Er zijn 3 manieren om zogenaamde loops te maken in C#:

  • while: zal 0 of meerdere keren uitgevoerd worden

  • do while: zal minimaal 1 keer uitgevoerd worden

  • for: een alternatieve iets compactere manier om loops te beschrijven

info
info
info
info
info
info
info
info
info
info
info
info
Kennisclip 1
Kennisclip 2
Kennisclip
Meer info over BMI
Kennisclip voor deze inhoud
Inleiding
Kennisclip voor deze inhoud
alle conversie-mogelijkheden hier bekijken

H6: Arrays

Oefeningen

Al deze oefeningen maak je in een klasse Loops

Oefeningen WHILE en DO WHILE

Oefening: H5-CountDown

Leerdoelen

  • flowchart omzetten naar code

  • gebruik van een while-lus

Functionele analyse

Je vraagt de gebruiker een positief geheel getal en vervolgens ga je aftellen. Alle getallen vanaf dat getal tot 1 worden getoond onder elkaar. Na het tonen van het laatste getal toon je “Start!”.

Technische analyse

Maak een methode met de naam CountDown

Zet volgende flowchart om in code:

Voorbeeldinteractie(s)

Testscenario's

Voer een negatief getal in.

Oefening: H5-Wachtwoord

Leerdoelen

  • Gebruik van een do while-lus

Functionele analyse

De gebruiker wordt gevraagd om een wachtwoord in te geven totdat het juiste antwoord (“AP”) wordt gegeven. Toon daarna “Wachtwoord in orde!”.

Tel ook het aantal pogingen dat de gebruiker nodig had om het juiste antwoord te geven en toon het aantal pogingen.

Technische analyse

Maak een methode met de naam Wachtwoord

Zet volgende flowchart om in code:

Voorbeeldinteractie(s)

Testscenario's

· Duw meteen op ENTER

Oefening: H5-Gemiddelde

Leerdoelen

  • Gebruik van een while of do while lus.

Functionele analyse

Bereken het gemiddelde van een aantal ingegeven getallen. De invoer van de getallen stopt met ingeven van de waarde 0.

Technische analyse

Maak een methode met de naam Gemiddelde. Het gemiddelde bereken je door de ingegeven getallen te delen door het aantal ingegeven getallen. De 0 die de reeks getallen stopt, wordt niet meegeteld als ingegeven getal.

Werk een oplossing uit met gebruik van een while lus of een do while lus. Zorg ervoor dat je gemiddelde ook de cijfers na de komma bevat.

Voorbeeldinteractie(s

Testscenario's

  • Test met negatieve waarden

  • Geef dadelijk een 0 in. Wat wordt er dan getoond als waarde van het gemiddelde en wat betekent dit?

Oefening: H5-Feestje

Leerdoelen

· Gebruik van een while met samengestelde booleaanse expressie

Functionele analyse

Je organiseert een feestje en met een programma ga je de inschrijvingen noteren. Je kan echter maximaal 20 personen inschrijven én je kan elk moment beslissen dat je geen volgende persoon meer wil inschrijven. Op het einde toon je alle namen van de ingeschrevenen.

Technische analyse

Maak een methode met de naam Feestje

Werk een oplossing uit met gebruik van een while lus.

Voorbeeldinteractie(s)

Testscenario's

  • Geef dadelijk “nee” in.

  • Probeer meer dan 20 personen in te schrijven.

Oefening: H5-AantalDigits

Leerdoelen

  • Gebruik van een do while

Functionele analyse

Schrijf een programma dat het aantal cijfers in een getal telt (het getal 12348 heeft bijvoorbeeld 5 cijfers). Het is de bedoeling dat je dit doet met een loop, dus niet door het getal als tekst te behandelen.

Technische analyse

Maak een methode met de naam AantalCijfers

Het is de bedoeling dat je het aantal digits telt met een do while loop, dus niet door het getal als tekst te behandelen.

Voorbeeldinteractie(s)

Oefening: H5-SomEvenGetallen

Leerdoelen

  • Gebruik van een while met een geneste if.

  • Flowchart omzetten in code

Functionele analyse

Schrijf een programma dat de som maakt van alle even getallen van 1 tot een waarde n. Toon niet enkel de eindsom maar ook alle even getallen met de tussentijdse sommen.

Technische analyse

Maak een methode met de naam SomEvenGetallen

Zet volgende flowchart om in code:

Voorbeeldinteractie(s)

Testscenario's

  • Druk dadelijk op ENTER.

  • Geef 0 in.

  • Geef 1 in.

Oefening: H5-Factoren

Leerdoelen

  • Gebruik van een while met geneste if’s.

Functionele analyse

Technische analyse

Maak een methode met de naam Factoren

Gebruik een while loop. Gebruik een extra if om ervoor te zorgen dat er op het einde van de factoren geen komma teveel staat.

Voorbeeldinteractie(s)

Testscenario's

  • Geef 0 in.

  • Geef 1 in.

  • Geef een negatief getal in.

  • Geef een priemgetal in.

Oefening: H5-RNA

Leerdoelen

· Gebruik van een do while met geneste if.

Functionele analyse

DNA heeft steeds een RNA-complement (DNA is het gevolg van RNA transscriptie). Schrijf een programma dat een ingevoerde DNA-string omzet naar een RNA-string. De gebruiker voert steeds 1 DNA-nucleotide (m.a.w. één letter) in per keer en duwt op enter, de RNA string wordt steeds groter. De omzetting is als volgt:

· G wordt C

· C wordt G

· T wordt A

· A wordt U

· “stop” dan stopt de gebruiker met letters ingeven en wordt het resultaat getoond.

· Andere letters of meer dan één letter worden genegeerd

Technische analyse

Noem de methode voor deze oefening RNATranscriptie.

Voorbeeldinteractie(s)

Testscenario's

  • Geef andere letters in dan G, T, C of A.

  • Geef meerdere letters in.

  • Geef een getal in.

  • Druk op ENTER.

Oefening: H5-boekhouder

Leerdoelen

  • Gebruik van een do while met geneste if

  • Gebruik van een oneindige lus

Functionele analyse

Maak een 'boekhoudprogramma': de gebruiker kan continu positieve en negatieve getallen invoeren. Dit programma houdt volgende zaken bij:

· de totale balans

· de som van de positieve getallen

· de som van de negatieve getallen

· het gemiddelde

Technische analyse

Maak een methode met de naam Boekhouder

Voor de eerste drie zaken kom je toe met een variabele. Voor de laatste is dit lastiger, omdat elk nieuw getal een kleiner effect heeft op het gemiddelde dan het vorige. Je houdt beter een teller bij met het aantal ingevoerde getallen. Dan is het gemiddelde de totale balans gedeeld door het aantal ingevoerde getallen.

voorbeeldinteractie(s)

(Dit programma kan blijven verder lopen zo lang je wil.)

Testscenario's

· Druk dadelijke op ENTER.

Oefeningen FOR

Oefening: H5-VanMin100Tot100

Leerdoelen

  • Gebruiken van een for-lus

  • Aanpassen 'update'

  • Flowchart omzetten in code

Functionele analyse

Basis: Toon alle natuurlijke getallen van -100 tot 100.

Uitbreiding: Toon alle even natuurlijke getallen van -100 tot 100.

Technische analyse

Maak een methode met de naam VanMin100Tot100.

Zet volgende flowcharts om in code: (als er geen update vermeld is, wordt de wachtervariabele verhoogd met 1)

Basis:

Uitbreiding:

voorbeeldinteractie(s)

Basis

Uitbreiding:

Oefening: H5-EenTafel

Leerdoelen

  • Gebruiken van een for-lus

Functionele analyse

Vraag aan de gebruiker van welk getal de tafel van vermenigvuldiging tot 10 moet getoond worden. Toon elke vermenigvuldiging onder elkaar.

Technische analyse

Maak een methode met de naam EenTafel.

voorbeeldinteractie(s)

Testscenario's

Geef 0 in.

Oefening: H5-Veelvouden6En8

Leerdoelen

  • Gebruiken van een for-lus met een geneste if met een samengestelde booleaanse expressie

Functionele analyse

Toon alle getallen van 1 tot en met 100 die een veelvoud zijn 6 en die een veelvoud zijn van 8.

Technische analyse

Maak een methode met de naam Veelvouden6En8.

voorbeeldinteractie(s)

Oefening: H5-Priemchecker

Leerdoelen

  • Gebruiken van een for-lus met geneste if.

Functionele analyse

Je krijgt een getal van de gebruiker. Je moet nagaan of dit een priemgetal is, d.w.z. of het precies 2 gehele delers heeft.

Technische analyse

Maak een methode met de naam PriemChecker.

Elk geheel getal vanaf 2 heeft minstens 2 gehele delers: 1 en zichzelf. Als dat de enige delers van het gegeven getal zijn, is het priem. Je kan dus nagaan of een getal een priemgetal is door alle getallen vanaf 2 tot het getal zelf te overlopen en na te gaan of deze delers zijn van het getal. Je mag veronderstellen dat de gebruiker minstens 2 intypt.

voorbeeldinteractie(s)

Testscenario's

Test met een negatief getal.

Oefening: H5-PriemGenerator

Leerdoelen

  • Gebruiken van een while lus met een geneste for lus

Functionele analyse

Je toont de priemgetallen tussen een laagste waarde en een hoogste waarde die door de gebruiker worden ingegeven.

Technische analyse

Maak een methode met de naam PriemGenerator.

Je kan een deel van de code van de vorige oefening gebruiken

voorbeeldinteractie(s)

Testscenario's

Test met een negatieve laagste waarde.

For

For

Het gebeurt heel vaak dat we een teller bijhouden die aangeeft hoe vaak een lus al doorlopen is. Wanneer de teller een bepaalde waarde bereikt moet de loop afgesloten worden.

Bijvoorbeeld volgende code om alle even getallen van 0 tot 10 te tonen:

int k = 0;
while(k <= 10)
{
    Console.WriteLine(k);
    k = k + 2;
}

Met een for-loop kunnen we deze veel voorkomende code-constructie verkort schrijven.

For syntax

De syntax van een for-loop is de volgende:

for (setup; test; update)
{
    // alles tussen deze accolades is de "body" van de lus
}
  • setup: In het setup gedeelte voeren we code uit die klaar moet zijn voor de lus voor het eerst begint. Dat betekent in de praktijk bijna altijd dat we een loop variabele aanmaken waarmee we bijhouden hoe vaak we al door de lus zijn gegaan. Variabelen die je hier aanmaakt hebben dezelfde scope als de body van de lus. Wanneer de for eindigt, bestaan ze met andere woorden niet meer.

  • test: Hier plaatsen we een booleaanse expressie die nagaat of de lus mag worden uitgevoerd. Deze heeft dus niet dezelfde functie als de booleaanse expressie van een while. Als ze false oplevert, wordt de body van de lus niet meer uitgevoerd. Dit betekent in de praktijk heel vaak dat we nagaan of de loop variabele al een zekere waarde heeft bereikt.

  • update: Hier plaatsen we wat er moet gebeuren telkens de loop body is afgewerkt. Meestal zullen we hier de loop variabele verhogen of verlagen.

In Flowgorithm (dit stemt helaas niet 100% overeen met C# omwille van een klein verschil in scope):

Gebruiken we deze kennis nu, dan kunnen we de eerder vermelde code om de even getallen van 0 tot en met 10 tonen als volgt:

for (int teller = 0; teller <= 10; teller += 2)
{
    Console.WriteLine(teller);
}

Voor de indexvariabele kiest men meestal niet teller, maar i, maar dat is niet noodzakelijk. In de setup wordt dus een variabele op een start-waarde gezet. De test zal aan de start van iedere loop kijken of de voorwaarde nog waar is, indien dat het geval is dan wordt een nieuwe loop gestart en wordt i met een bepaalde waarde, zoals in update aangegeven, verhoogd.

Je kan niets met een for dat je met een while niet kan. Je kan niets met een while dat je met een for niet kan. Als je dit niet inziet, heb je lussen nog niet begrepen en moet je dit hoofdstuk vanaf het begin opnieuw lezen.

P.S.: wat voor lus is hier net gebruikt?!

Waarom stemt de Flowgorithm hierboven niet perfect overeen met de C#-code? Omdat in de C#-code teller gedeclareerd wordt als onderdeel van de for. In de code die we uit de Flowgorithm hierboven kunnen genereren, wordt teller niet gedeclareerd als onderdeel van de lus en blijft deze dus bestaan nadat de lus is afgewerkt. Dit is een klein detail, maar we vinden het belangrijk uit te leggen waarom deze code niet gelijkwaardig is.

While en Do While

While

Voorbeeld werking

Volgend Flowgorithm programma demonstreert hoe een while lus wordt toegepast wanneer je systematisch boodschappen afwerkt.

Omdat de True-tak terug leidt naar de controle van de voorwaarde, blijven we dezelfde stappen herhalen tot niet meer aan de voorwaarde voldaan is. Dit is het basisconcept achter een while lus. Het woord "while" betekent dan ook "zolang als".

Dit is anders dan bij if: het woordje "if" betekent gewoonweg "als" en houdt dus niet in dat we dezelfde stappen zullen herhalen. Bij while blijven we dezelfde stappen uitvoeren zo lang dat nodig is.

Syntax

De syntax van een while loop is eenvoudig:

while (booleaanse expressie) 
{
  // C# die zal uitgevoegd worden zolang de booleaanse expressie waar is
}

Waarbij, net als bij een if statement, de conditie uitgedrukt wordt als een booleaanse expressie.

Zolang de conditie true is zal de code binnen de accolades uitgevoerd worden. Indien dus de conditie reeds vanaf het begin false is dan zal de code binnen de while-loop niet worden uitgevoerd.

Telkens wanneer het programma aan het einde van het while codeblock komt springt het terug naar de conditie bovenaan en zal de test wederom uitvoeren. Is deze weer true dan wordt de code weer uitgevoerd. Van zodra de test false is zal de code voorbij het codeblock springen en na het while codeblok doorgaan.

De code voor ons boodschappenlijstje ziet er dan ook zo uit:

Een tweede voorbeeld van een eenvoudige while loop:

int myCount = 0;

while (myCount < 100)
{
    myCount++;
    Console.WriteLine(myCount);
}

Zolang myCount kleiner is dan 100 (myCount < 100) zal myCount met 1 verhoogd worden en zal de huidige waarde van myCount getoond worden. We krijgen met dit programma dus alle getallen van 1 tot en met 100 op het scherm onder elkaar te zien.

Daar de test gebeurt aan het begin van de loop wil dit zeggen dat het getal 100 nog wel getoond zal worden. Begrijp je waarom? Test dit zelf!

Do while

In tegenstelling tot een while loop, zal een do-while loop sowieso minstens 1 keer uitgevoerd worden.

Voorbeelden werking

Volgende flowchart helpt je bepalen of je klaar bent om een commit operatie uit te voeren in Git. Zoals je in andere vakken leert, moet je in Git eerst de gewenste bestanden in de staging area zetten. Dat kan soms in één commando gebeuren en soms in meerdere commando's, maar je moet altijd wel minstens één keer stagen.

Een tweede voorbeeld: een opstel schrijven. Dat moet je altijd nog eens nakijken. Als het niet goed is, moet je je tekst aanpassen en opnieuw controleren. Pas wanneer je helemaal tevreden bent, mag je inzenden.

De syntax van een do-while is eveneens verraderlijk eenvoudig:

do{
      // C# die uitgevoerd zal worden zolang de booleaanse expressie waar is
} while (booleaanse expressie);

Merk op dat achteraan de conditie een puntkomma na het ronde haakje staat. Dit is een véél voorkomende fout. Bij een while is dit niet zo! Daar de test van een do-while achteraan de code van de loop gebeurt is het logisch dat een do-while dus minstens 1 keer wordt uitgevoerd. Het volgende eenvoudige aftelprogramma toont de werking van de do-while loop.

Voor het opstel ziet dit er bijvoorbeeld zo uit:

Je kan dit ook doen met een gewone while (je kan elke do while herschrijven met een gewone while), maar dan kost dat meer code. Dat verhoogt dan weer de kans op fouten.

Complexe condities

Uiteraard mag de conditie waaraan een loop moet voldoen complexer zijn door middel van de relationele operatoren.

Volgende while bijvoorbeeld zal de gebruiker aanraden in bed te blijven zolang deze ziek of moe is:

while(gebruikerVoeltZichZiek || gebruikerVoeltZichMoe)
{
  Console.WriteLine("Blijf in bed!");
  Console.ReadLine();
}

Oneindige loops

Indien de loop-conditie nooit false wordt, dan heb je een oneindige loop gemaakt. Soms is dit gewenst gedrag (bijvoorbeeld in een programma dat constant je scherm moet updaten, zoals in een game), soms is dit een bug.

Volgende twee voorbeelden tonen dit:

  • Een bewust oneindige loop:

    while(true)
    {
      // code die nooit hoort te stoppen of die zelf het programma kan afsluiten
    }
  • Een bug die een oneindige loop veroorzaakt:

    int teller = 0; 
    while(teller<10)
    {
      Console.WriteLine(teller);
    }

Scope van variabelen in loops

Volgende code toont bijvoorbeeld foutief hoe je de som van de eerste 10 getallen (1+2+3+...+10) zou maken:

int teller= 1;
while(teller <= 10)
{
   int som = 0;
   som = som+teller;
   teller++;
}
Console.WriteLine(som); //deze lijn zal fout genereren

De correcte manier om dit op te lossen is te beseffen dat de variabele som enkel binnen de accolades van de while-loop gekend is. Op de koop toe wordt deze steeds terug op 0 gezet en er kan dus geen som van alle teller-waarden bijgehouden worden:

int teller= 1;
int som=0;  
while(teller <= 10)
{
   som = som+teller;
   teller++
}
Console.WriteLine(som);

Debuggen

Debuggen

Debugging is een essentiële skill. Bekijk onderstaande clip en zorg dat je de werking van elk gedemonstreerd onderdeel begrijpt. Gebruik voortaan altijd de debugger als je programma wel uitvoert, maar verkeerde resultaten produceert vooraleer je de lector aanspreekt.

Intussen kan je hopelijk stap voor stap een Flowgorithm programma in je hoofd uitvoeren. Als je de software gedownload hebt, kan je dit ook op je machine doen door telkens F6 te gebruiken. Je kan ook op elk punt in het programma kijken wat de waarden van je variabelen zijn via de knop met het symbooltje x=.

Voor C#-programma's is er een gelijkaardige tool in Visual Studio Code: de debugger. De naam is wat misleidend: deze zal geen bugs verwijderen uit je programma, maar zal je wel helpen stap voor stap door je programma te gaan. Als je programma bugs vertoont, is de kans groot dat je op deze manier de oorzaak kan vinden.

Dit wordt geïllustreerd door volgende code:

using System;

namespace Demonstratie_debugger
{
    class Program
    {
        public static void MethodeB() {
            int getal = 2;
            double kommagetal = 4.9;
            Console.WriteLine("B");
        }
        public static void MethodeA() {
            Console.WriteLine("A1");
            MethodeB();
            int getal1 = 56;
            int getal2 = 13;
            Console.WriteLine("A2");
        }
        static void Main(string[] args)
        {
            Program.MethodeA();
        }
    }
}

Eerst zet je ergens in deze code een breakpoint. Dit ziet er uit als een rood bolletje naast de code waar je programma moet "stilstaan". Je doet dit door op de plaats van het bolletje te klikken.

Dan start je je programma op via "Run -> Start debugging" (of via F5). De regel waarop je code gepauzeerd is, wordt aangeduid.

In dit geval is er maar één instructie: "voer MethodeA uit". We kunnen dit als één geheel doen via "step over" of we kunnen ook de stappen binnenin MethodeA bekijken via "step into".

In dit geval kunnen we in detail kijken hoe MethodeA werkt via "step into". We kunnen "step into" niet altijd uitvoeren: we hebben hiervoor toegang nodig tot de broncode van de instructie die we verder willen bekijken.

Door enkele stappen uit te voeren, kunnen we pauzeren op de toekenning van getal2:

Op dit punt in de code, kunnen we onder "variables" bekijken welke waarden onze variabelen op dit moment hebben (merk op: de toekenning van 13 is nog niet gebeurd, we staan stil vlak ervoor):

En via de "call stack" zien we dat we niet gewoon in MethodeA zitten, maar dat MethodeA is opgeroepen door Main:

Er komen geen oefeningen specifiek rond debuggen. Dat gaat niet, want debuggen helpt je alleen informatie verzamelen. Toch is het een zéér, zéér belangrijke skill. Debuggen kan je helpen inzien wat je programma doet. Er worden geen rechtstreekse punten op gegeven, maar als je goed debugt, kan je opdrachten afwerken die je anders niet zou kunnen maken. Gedraagt je programma zich tijdens de labosessies niet zoals je verwacht? Zet een breakpoint en kijk wat er aan de hand is!

Alternatieve syntax

De reeds besproken manier om arrays te maken is veelzijdig en toont alle aspecten, maar vraagt vrij veel schrijfwerk. Er zijn nog manieren om arrays aan te maken, maar deze veronderstellen dat je de array in één keer kan opvullen.

Manier 1: met new

Ook op deze manier moet je array gedeclareerd worden als volgt:

Dit is hetzelfde als eerder. Je kan bijvoorbeeld volgende concrete code hebben:

Nu kan je deze array meteen initialiseren op een andere manier dan eerder. Je kan kiezen de grootte niet in te vullen en de gebruikte waarden tussen accolades te zetten, bijvoorbeeld:

Er staan vijf elementen tussen de accolades, dus de compiler kan achterhalen dat je een nieuwe array van vijf elementen wil maken, zonder dat je dat uitdrukkelijk zegt.

Manier 2: verkorte declaratie en initialisatie

Indien je direct waarden wilt toekennen (initialiseren) tijdens het aanmaken van de array zelf dan mag dit ook. We noemen dit de array literal syntax:

Hier zal je array dus een vaste lengte van 5 elementen hebben. Merk op dat deze manier dus enkel werkt indien je al weet hoe groot je array moet zijn voor je je programma opstart. De andere opties bieden in dat opzicht meer flexibiliteit ten koste van meer schrijfwerk.

Array principes

Array principes

Arrays

Stel je voor dat je, met de kennis die je nu hebt, een boodschappenlijstje moet programmeren. Met andere woorden, een programma dat vraagt om een aantal regels tekst in te typen, waarbij je dan items kan aanduiden als gekocht.

Je zou ruimte kunnen voorzien voor 3 items, voorgesteld als string, als volgt:

Een groot nadeel van deze aanpak: als je vier items wil ondersteunen, heb je meer code nodig. Als je vijf items wil ondersteunen, heb je nog meer code nodig. Deze code kan geen basis zijn voor een degelijk boodschappenlijstje.

Een tweede mogelijkheid bestaat erin heel het lijstje voor te stellen als een string, als volgt:

Hiermee kan je elk gewenst aantal items toevoegen, maar de voorstelling is erg onhandig. Als je bijvoorbeeld item zeven op je lijstje wil zoeken, moet je plustekens eerst zes plustekens tellen. Als je het lijstje op een andere manier wil tonen aan de gebruiker, bijvoorbeeld met één item per regel, is dat lastig. Als je wil bijhouden of iets gekocht is, kan je daar geen boolean voor gebruiken, maar moet je bijvoorbeeld een vinkje vlak voor het item plaatsen. Dat is allemaal vrij ingewikkelde code voor een vrij eenvoudige taak.

Arrays bieden een oplossing voor deze problemen. Arrays zijn reeksen van waarden, maar ze zijn zelf ook waarden. Met andere woorden, ze maken het mogelijk één variabele te maken die verschillende waarden voorstelt. Hierdoor wordt je code leesbaarder en eenvoudiger in onderhoud. Arrays zijn een zeer krachtig hulpmiddel, maar er zitten wel enkele addertjes onder het gras.

Laat ons beginnen bij het begin: een array is een reeks waarden van één type en heeft een bepaalde lengte. Bijvoorbeeld een reeks van 10 ints of 5 strings. Je kan dus geen reeks hebben met een mengeling van int en string, of van boolean en double, of wat dan ook. Je kan een array toekennen aan een variabele, maar je kan ook individuele waarden uit de reeks halen door middel van een index. Dit is een getal dat een positie in de reeks aanduidt, zoals je dat ook kent van de substring methode.

We zullen het eerst even voordoen in Flowgorithm.

Basisgebruik arrays

Arrays declareren

Net als andere datatypes moet je aangeven dat een variabele van een bepaald array type is. Je moet dus de compiler informeren dat er een array bestaat met een bepaalde naam en een bepaald type. Dit is bijna hetzelfde als het declareren van een variabele van een van de types die je al kent, maar je geeft aan dat het om een reeks gaat door middel van rechte haken.

We geven hier enkele voorbeelden:

De rechte haken betekenen dus "een reeks van" en een declaratie van een reeks kan een heleboel declaraties van individuele variabelen vervangen. Met deze voorstelling heb je bijvoorbeeld item1 en item2 en item3 niet meer nodig.

Als we naar de Flowgorithm code van het boodschappenlijstje kijken, zien we dit ook:

Merk wel op: hier vindt ook meteen een initialisatie plaats.

Een initialisatie vertelt de compiler niet alleen dat deze variabele bestaat, maar ook wat zijn eerste waarde is. Een waarde van een array-type stelt eigenlijk een hoeveelheid ruimte in het geheugen voor. Ruimte voor een bepaald aantal strings, voor een bepaald aantal getallen, maakt niet uit. Om technische redenen die we later in meer detail bekijken, moet je deze ruimte reserveren met het keyword new. Je moet ook zeggen hoe veel ruimte je nodig hebt. Bijvoorbeeld, met de declaraties van hierboven:

Zoals je merkt uit het laatste voorbeeld, mag je deze waarde ook berekenen. Ook het Flowgorithm voorbeeld toont dit, want daar wordt aantalItems ingelezen.

Arrays opvullen

Nadat de nodige ruimte voorzien is, kan je deze gebruiken. Dit doe je door te zeggen dat een bepaalde waarde op een bepaalde positie in de reeks terechtkomt. Deze positie noemen we de index van het element. Het eerste element krijgt index 0, niet 1. Het laatste krijgt een index gelijk aan de lengte van de array verminderd met 1.

We geven even een voorbeeld rechtstreeks in C#:

Dit hoeven geen vaste waarden te zijn! Je kan ook dit doen:

Dit zorgt dat je code kan uitsparen. Met 10 individuele variabelen, dus zonder array, zou je bovenstaande code ongeveer zo moeten schrijven:

Maar met een array heb je ook deze optie:

En als je je lijstje wil uitbreiden tot 1000 items, moet je alleen de 10 door 1000 vervangen. We zullen nog vaak gebruik maken van arrays en lussen om herhaalde code te vermijden!

Arrays uitlezen

Je gebruikt de notatie met rechte haken ook om een array uit te lezen:

Ook hier bespaar je vervelend werk met een lus:

Als je een element probeert uit te lezen dat nog geen waarde gekregen heeft, krijg je een defaultwaarde. Wat voor waarde dat is, hangt af van de array. Er zit een systeem achter, maar dat behandelen we pas later in de cursus. Voorlopig mag je dit onthouden: voor getallen krijg je 0. Voor booleans krijg je false. Voor strings krijg je een speciale waarde null, die verschillend is van "". intWat je vooral moet weten over null is dat je er geen eigenschappen van kan opvragen of methodes op kan toepassen. Ik laat even zien wat ik bedoel.

Dus als een variabele null bevat of kan bevatten, moet je syntax vermijden die hier een punt achter plaatst. Je kan dit op een eenvoudige manier doen met if (mijnVariabele is null) { ... } else { ... }.

De lengte van de array te weten komen

Soms kan het nodig zijn dat je in een later stadium van je programma de lengte van je array nodig hebt. De Length eigenschap geeft je deze lengte. Volgend voorbeeld toen dit:

Het voordeel hiervan is je maar één keer moet vastleggen hoe groot de array is en daarna in het algemeen over de grootte kan spreken. Zo kan je bij het aanpassen van je code niet vergeten overal de juiste aanpassing te doen. Let op: de lengte geeft niet aan hoe veel items je al hebt ingevuld! Ze geeft aan hoe veel plaats er maximaal is in de array.

Werken met arrays

Nuttige array methoden

Iedere array heeft een aantal methoden waar meteen gebruik van gemaakt kan worden. Al deze methoden hier beschrijven zou ons te ver nemen, maar enkele methoden zijn echter zeer handig om te gebruiken en hebben een vanzelfsprekende betekenis: Max(), Min(), Sum() en Average().

Volgende code geeft bijvoorbeeld het grootste getal terug uit een array genaamd "leeftijden".

Met de rest kan je het minimum, de som en het gemiddelde bepalen.

Daarnaast beschikken arrays ook over functionaliteit die niet specifiek gelinkt is aan getallen. We bekijken hier een paar mogelijkheden die heel vaak van pas komen. Voor deze methoden is de syntax, om redenen die we later zullen zien, wat anders: je schrijft eerst Array. en je geeft de array in kwestie mee tussen de haakjes.

Sort: Arrays sorteren

Om arrays te sorteren roep je de Sort()-methode op als volgt, als parameter geef je de array mee die gesorteerd moet worden.

Volgende voorbeeld toont hier het gebruik van:

Wanneer je de Sort-methode toepast op een array van strings dan zullen de elementen alfabetisch gerangschikt worden. Merk op dat je niet meteen iets merkt nadat je Sort hebt opgeroepen. Intern zijn er normaal elementen van plaats gewisseld, maar je ziet dit pas als je bijvoorbeeld de elementen afprint.

Reverse: Arrays omkeren

Met de Array.Reverse()-methode kunnen we dan weer de elementen van de array omkeren (dus het laatste element vooraan zetten en zo verder.

Ook hier zie je pas iets als je de elementen toont.

IndexOf: Zoeken in arrays

De IndexOf-methode maakt het mogelijk om te zoeken naar de index van een gegeven element in een index. De methode heeft twee parameters, enerzijds de array in kwestie en anderzijds het element dat we zoeken. Als resultaat wordt de index van het gevonden element teruggegeven. Indien niets wordt gevonden zal het resultaat (in de praktijk) -1 zijn.

Volgende code zal bijvoorbeeld de index teruggeven van het item "koffie" indien dit in de array items staat:

Volgend voorbeeld toont het gebruik van deze methode:

IndexOf kan erg van pas komen om stukjes informatie aan elkaar te linken. Bijvoorbeeld: de prijs van de producten staat steeds op dezelfde index in de andere array:

We weten nu genoeg om de gebruiker de prijs van elk product te kunnen tonen.

We tonen nu hoe we eerst het juiste product zoeken en dan vervolgens die index bewaren en gebruiken om de prijs te tonen:

Parameters

Wat zijn parameters?

Een methode is een stappenplan voor een bepaalde taak. Met de syntax die we tot hiertoe gezien hebben, wordt deze taak altijd op exact dezelfde manier uitgevoerd. Dat beperkt de mogelijkheden enorm.

Een niet-digitaal voorbeeld. Ik plan een feest. Een van mijn deeltaken is om een simpele appeltaart te maken. De tweede is om een simpele perentaart te maken.

Het stappenplan voor de appeltaart is als volgt:

  1. leg taartdeeg in een ronde bakvorm

  2. doe er wat pudding op

  3. snijd een appel in stukken

  4. beleg met de stukjes fruit

  5. zet in de oven

Het stappenplan voor de perentaart is als volgt:

  1. leg taartdeeg in een ronde bakvorm

  2. doe er wat pudding op

  3. snijd een peer in stukken

  4. beleg met de stukjes fruit

  5. zet in de oven

Dit is hetzelfde stappenplan, op het gebruikte fruit na. Eigenlijk kan je in stap 3 allerlei soorten fruit gebruiken, afhankelijk van het resultaat dat je wenst. We kunnen het stappenplan dus algemener formuleren:

  1. leg taartdeeg in een ronde bakvorm

  2. doe er wat pudding op

  3. snij het stuk fruit dat je wil gebruiken in stukken

  4. beleg met de stukjes fruit

  5. zet in de oven

Op het moment dat we een taart willen bakken, moeten we pas voor een bepaald stuk fruit kiezen. In de tekst hierboven is "het stuk fruit" dus een soort variabele: we schrijven een naam maar we bedoelen daarmee een waarde die in de uitvoering wordt vastgelegd.

In code zijn dit soort variabelen parameters van een methode. In de definitie van de methode werken we met hun naam (zoals "het stuk fruit"). Wanneer we de methode oproepen, voorzien we hun waarde (zoals een appel of een peer of een ananas). In de call spreken we ook over argumenten in plaats van parameters. De termen worden soms door elkaar gehaald, maar ze betekenen dus niet exact hetzelfde.

In Flowgorithm kunnen kort noteren dat we een appeltaart en een perentaart willen maken:

In de praktijk maken we natuurlijk geen taarten, maar doen we bewerkingen met gegevens. Volgende methode berekent en toont een bepaalde macht van een bepaald getal. De werkwijze is altijd dezelfde, maar de gebruiker beslist zelf welke macht hij wil berekenen en van welk getal:

Dit is een definitie van een methode, dus vergelijkbaar met een stappenplan of een flowchart. We geven aan dat er twee stukjes informatie zijn die per uitvoering kunnen verschillen: het grondtal en de macht. Omdat dit eigenlijk variabelen zijn (met een scope beperkt tot deze methode) geven we ook hun type, net zoals bij variabelen die we op de reeds gekende wijze declareren.

Als we deze methode willen gebruiken, kunnen we dit doen:

De laatste regel bevat de call van de methode. De definitie bevat een paar "gaten" en de call vult deze in met de gewenste getallen. Het is belangrijk dat de gebruikte types en de verwachte types compatibel zijn. Het is bijvoorbeeld niet mogelijk Macht("Hello World","blabla"); te schrijven als call want strings zijn geen ints.

De verbinding van definitie en oproep

Als je het stappenplan bovenaan deze pagina gebruikt om een appeltaart te maken, heb je achteraf geen appel meer. Hier kan het mechanisme van methodes je wat verrassen. Je moet in het achterhoofd houden dat de parameters van een methode hetzelfde werken als gewone variabelen.

Een voorbeeld:

Dit programma zal je eerst 3 tonen en dan, op de volgende regel, 4. Dit komt omdat de = op regel 2 niet betekent "a is hetzelfde als b", maar wel "kopieer het ding met naam a en geef de kopie de naam b". Dit is hoe toekenning werkt met alle types die we tot hiertoe gezien hebben.

Dit is niet hoe toekenning altijd werkt. Met name voor arrays is het wat anders, maar dat geval zullen we later in meer detail behandelen.

Met argumenten van een methode-oproep is het hetzelfde. Nog een voorbeeld:

Je ziet dat het getal enkel gewijzigd is binnen de methode VeranderGetal en daarna weer teruggezet lijkt op de oude waarde. Dit is niet helemaal correct. Wat eigenlijk gebeurt, is het volgende:

  1. een waarde 4 krijgt de naam mijnGetal

  2. de waarde met naam mijnGetal wordt getoond

  3. de waarde met naam mijnGetal wordt gekopieerd en krijgt de naam getal

  4. de naam mijnGetal wordt toegekend aan het getal dat je verkrijgt door de kopie met één te verminderen en verwijst nu dus naar een 3

  5. deze 3 wordt getoond

  6. de oproep eindigt

  7. heel de tijd is mijnGetal blijven verwijzen naar 4 en daarom wordt er terug een 4 getoond

mijnGetal en getal hebben dus nooit dezelfde data bevat. Bij het begin van een oproep van VeranderGetal zijn hun data wel kopieën van elkaar, maar na regel 2 is zelfs dat niet meer waar.

Gekende parameters

Je hebt al vaak waarden als argument meegegeven:

  • de tekst die je laat zien via WriteLine

  • de grenzen waarbinnen een willekeurig getal bepaald moet worden met Next

  • de tekst die je wil vervangen en de tekst die hem vervangt met Replace

Ook hier wordt altijd hetzelfde stappenplan gevolgd, maar de manier waarop dat plan doorlopen wordt, verschilt afhankelijk van de gebruikte argumenten.

De rol van scope

Voor de eenvoud hebben we in bovenstaand voorbeeld twee namen gebruikt: mijnGetal en getal. Eigenlijk hoefde dat niet. We kunnen ook dit doen:

De parameter getal van VeranderGetal is niet dezelfde variabele als de variabele getal van Main. Elke functiedefinitie bakent een scope af. Vermits de definitie van VeranderGetal niet genest is in de definitie van Main (en het omgekeerde ook niet waar is) staan hun scopes los van elkaar. Binnenin Main bestaat er een variabele met naam getal, binnenin VeranderGetal bestaat er een andere variabele met naam getal.

Er staat wel een oproep van VeranderGetal in Main, maar dit heeft geen effect op de variabelen in scope. Enkel de definities zijn hier van belang.

Geavanceerde methoden

Named parameters

Wanneer je een methode aanroept is de volgorde van je argumenten belangrijk: deze moeten meegeven worden in de volgorde zoals de methode parameters ze verwachten.

Met behulp van named parameters kan je echter expliciet aangeven welke argument aan welke methode-parameter moet meegegeven worden.

Stel dat we een methode hebben met volgende signatuur:

Zonder named parameters zou een aanroep van deze methode als volgt kunnen zijn:

We kunnen named parameters aangeven door de naam van de parameter gevolg door een dubbel punt en de waarde. Als we dus bovenstaande methode willen aanroepen kan dat ook als volgt met named parameters:

of ook:

Volgorde van named parameters belangrijk

Je mag ook een combinatie doen van named en gewone parameters, maar dan is de volgorde belangrijk: je moet je dan houden aan de volgorde van de methode-volgorde. Je verbetert hiermee de leesbaarheid van je code dus (maar krijgt niet het voordeel van een eigen volgorde te hanteren). Enkele voorbeelden:

Enkele NIET GELDIGE voorbeelden:

Optionele parameters

Soms wil je dat een methode een standaard waarde voor een parameter gebruikt indien de programmeur in z'n aanroep geen waarde meegaf. Dat kan met behulp van optionele of default parameters.

Je geef aan dat een parameter optioneel is door deze een default waarde te geven in de methode-signatuur. Deze waarde zal dan gebruikt worden indien de parameter geen waarde van de aanroeper heeft gekregen.

Optionele parameters worden steeds achteraan de parameterlijst van de methode geplaatst .

In het volgende voorbeeld maken we een nieuwe methode aan en geven aan dat de laatste parameters optioneel zijn. discountPercentage past een "gewone" korting toe op de aankoop van het aantal items. doubleDiscount is een speciale extra korting die het percentage verdubbelt. We veronderstellen dat items meestal verkocht worden zonder korting:

Volgende manieren zijn nu geldige manieren om de methode aan te roepen:

Je mag enkel de optionele parameters van achter naar voor weglaten. Volgende aanroep is dus niet geldig:

Door de argumenten te benoemen, kunnen we dit indien gewenst omzeilen. Volgende aanroep is wel geldig:

Method overloading

Method overloading wil zeggen dat je een methode met dezelfde naam en returntype meerdere keren definieert maar met andere parameters qua type en aantal. De compiler zal dan zelf bepalen welke methode moet aangeroepen worden gebaseerd op het aantal en type parameters dat je meegeeft.

Volgende methoden zijn overloaded:

Afhankelijk van de aanroep zal dus de ene of andere uitgevoerd worden. Volgende code zal dus werken:

Method overloading is de reden waarom je in Visual Studio Code de documentatie van meerdere versies van een methode kan bekijken. Dit doe je door op de pijltjes naast de info over die methode te klikken.

Methoden intro

Methoden

Veel code die we hebben geschreven wordt meerdere keren, al dan niet op verschillende plaatsen, gebruikt. Dit verhoogt natuurlijk de foutgevoeligheid. Door het gebruik van methodes kunnen we de foutgevoeligheid van de code verlagen omdat de code maar op 1 plek staat én maar 1 keer dient geschreven te worden. Ook de leesbaarheid en dus onderhoudbaarheid van de code wordt verhoogd wanneer we methoden gebruiken om verschillende deeltaken van elkaar te scheiden.

Wat is een methode

Een methode (bijna hetzelfde als een "functie" in andere programmeertalen) genoemd, is in C# een stuk code ('block') bestaande uit een 0, 1 of meerdere statements. Het is eigenlijk een klein stappenplan voor een onderdeel van je totale programma.

Zoals bij elk stappenplan is er een verschil tussen het vastleggen van de stappen en het uitvoeren. Het vastleggen van de stappen noemen we definiëren van de methode. Vergelijk met het tekenen van een flowchart voor een bepaalde taak, maar dan in code: de definitie van de methode stemt overeen met de flowchart die de deeltaak beschrijft.

Het uitvoeren van de stappen, dus het volgen van de flowchart, noemen we het oproepen of uitvoeren van de methode. De code die dit proces in gang zet noemen we een oproep (in het Engels: call) van de methode.

Je hebt intussen al vaak (ingebouwde) methodes opgeroepen, waaronder:

  • de Main methode van de klasse Program (voor deze hoef je de call zelf niet te schrijven, ze start automatisch op)

  • de WriteLine methode van de klasse Console

  • de Substring methode van een stuk tekst

Basissyntax

Definitie

In het begin schrijven we onze methodes zo (binnenkort zien we uitbreidingen):

Dit stukje code definieert dus hoe het stappenplan er uitziet. Het zorgt er niet voor dat het ook ooit wordt uitgevoerd. Daar is een call voor nodig. public zorgt dat deze methode opgeroepen kan worden van uit andere klassen, static is nodig omdat we klassen alleen als organisatieprincipe voor onze code gebruiken, void behandelen we iets verder op. Deze zaken, samen met de naam en de haakjes, noemen we ook de signatuur van de methode.

In Flowgorithm stemt de definitie van een methode overeen met een flowchart die in haar geheel getoond kan worden. Onderstaand voorbeeld bevat drie definities (flowcharts): Main, ToonGroen en ToonBlauw.

Je kan elke definitie (flowchart) terugvinden via dit menu:

Oproep

Om de methode te gebruiken, moeten we een statement uitvoeren die er als volgt uitziet:

Aangezien elk programma begint met een oproep van de Main methode, zal dit programma dus wel de tekst Groen laten zien en niet de tekst Blauw:

In Flowgorithm herken je de oproep als volgt:

Een oproep van een methode betekent dus dat je een andere flowchart uitvoert alsof het één stap is van de flowchart waarin je bezig bent.

Net zoals eerder kan je dit ook in C# stap voor stap uitvoeren door de debugger te gebruiken. Om de werking van een methode in detail te zien, gebruik je "step into".

Oefeningen

Oefeningen

Al deze oefeningen schrijf je in een klassen Methodes.

Oefening H7-ReeksOperaties

Leerdoelen

  • Methodes definiëren met parameters

  • Oproepen van de methodes

Functionele analyse

Je stelt een aantal typische berekeningen voor met methodes.

Technische analyse

Voor deze oefening schrijf je meerdere methodes. Je plaatst er slechts één in je keuzemenu, namelijk ReeksOperaties. Deze voert alle andere methodes van deze oefening één voor één uit.

Enkel ReeksOperaties mag Console.ReadLine statements bevatten. De andere methodes tonen hun resultaat op het scherm door middel van Console.WriteLine statements in de methode zelf die het resultaat bepaalt. (Met andere woorden, alle methodes zijn void.)

De andere methodes zijn:

  • BerekenStraal, die de straal van een cirkel kan berekenen waarvan je de diameter meegeeft (de diameter geef je mee als argument). De straal is de helft van de diameter.

  • Idem voor BerekenOmtrek en BerekenOppervlakte. De omtrek is de diameter maal het getal pi, dat je voorstelt als Math.Pi. De oppervlakte is straal maal straal maal pi.

  • Methode Maximum die het grootste van 2 getallen teruggeeft (beide getallen geef je mee als argument).

  • Methode IsEven die toont of een getal even of oneven is.

  • Methode ToonOnEvenGetallen die alle oneven getallen van 1 tot n toont waarbij n als argument wordt meegegeven.

Voorbeeldinteractie

Oefening H7-EmailadresGenerator

Leerdoelen

  • Methodes definiëren met return type en parameters

  • Oproepen van de methodes

Functionele analyse

Schrijf een programma dat op basis van je voornaam, naam en het feit of je al dan niet een student bent een AP e-mailadres genereert. Het e-mailadres moet uit allemaal kleine letters bestaan en moet een geldig e-mailadres zijn.

Je mag geen String methodes gebruiken maar je schrijft je eigen methodes. Als aanzet is de methode StringToLower al uitgewerkt.

Uitbreiding: kijk of voor al je lectoren en medestudenten het gegenereerde e-mailadres geldig is. Pas eventueel je code aan indien niet.

Technische analyse

Werk volgend flowchart verder af en zet om in C#.

Voorbeeldinteractie

Oefening: H7-ReeksOperatiesMetReturn

Leerdoelen

  • Methodes definiëren met parameters en returnwaarde

  • Oproepen van de methodes

  • Gebruiken van returnwaarden

Functionele analyse

Je stelt een aantal typische berekeningen voor met methodes die geschikt zijn voor gebruik in andere programma's.

Technische analyse

Voor deze oefening schrijf je meerdere methodes. Je plaatst er slechts één in je keuzemenu, namelijk ReeksOperatiesMetReturn. Deze voert alle andere methodes van deze oefening één voor één uit.

Enkel ReeksOperaties mag Console.ReadLine of Console.WriteLine statements bevatten. De andere methodes berekenen hun resultaat, zonder het te tonen op het scherm. Met andere woorden, hun return type is niet void.

De andere methodes zijn:

  • BerekenStraalMetReturn, die de straal van een cirkel kan berekenen waarvan je de diameter meegeeft (de diameter geef je mee als argument). De straal is de helft van de diameter. Voor je deze toont, rond je ze in ReeksOperatiesMetReturn af tot één cijfer na de komma met Math.Round(reedsBerekendeStraal,1).

  • Idem voor BerekenOmtrekMetReturn en BerekenOppervlakteMetReturn. Ook deze waarden moet je, nadat ze uitgerekend zijn, afronden tot 1 cijfer na de komma.

  • Methode MaximumMetReturn die het grootste van 2 getallen teruggeeft (beide getallen geef je mee als argument).

  • Methode IsEvenMetReturn die bepaalt of een getal even of oneven is. De returnwaarde vertelt dus of iets waar of niet waar is.

  • Methode BepaalEvenGetallenMetReturn. Deze geeft als antwoord een List van alle even getallen tot n. ReeksOperatiesMetReturn toont deze getallen dan, gescheiden door een komma.

Voorbeeldinteractie

Zoals boven, met de vermelde aanpassingen (afronding tot 1 cijfer na de komma en getallen gescheiden door ,)

H7-driehoeken

Functionele analyse

We schrijven een simpel tekenprogramma, dat driehoeken van een bepaalde afmeting, met een bepaald patroon kan tekenen.

Technische analyse

Schrijf een methode TekenDriehoek. Deze methode verwacht twee zaken: een karakter om het patroon voor te stellen en de hoogte van de driehoek. Ze roept zelf meermaals een andere methode TekenRegel op, die één regel van de driehoek tekent. TekenRegel toont niets op het scherm via Console.WriteLine!

Voorbeeldinteractie

List<T>

Een List<> collectie is de meest standaard collectie die je kan beschouwen als een flexibelere variant op een een doodnormale array.

De Generieke List<> klasse bevindt zich in de System.Collections.Generic namespace. Je dient deze namespace dus als using bovenaan toe te voegen wil je deze klasse kunnen gebruiken.

List aanmaken

De klasse List<T> is een zogenaamde generieke klasse. Tussen de < >tekens plaatsen we het type dat de lijst zal moeten gaan bevatten. Vaak wordt dit genoteerd als T voor "type". Bijvoorbeeld:

  • List<int> alleGetallen= new List<int>();

  • List<bool> binaryList = new List<bool>();

  • List<Pokemon> pokeDex = new List<Pokemon>();

  • List<string[]> listOfStringarrays = new List<string[]>();

Zoals je ziet hoeven we bij het aanmaken van een List geen begingrootte mee te geven, wat we wel bij arrays moeten doen. Dit is een van de voordelen van List: ze groeien mee. Als we toch een begingrootte meegeven (zoals in de kennisclip even getoond wordt) is dat enkel om de performantie van de code wat te verhogen in bepaalde scenario's. Wij gaan dit nooit doen.

Elementen toevoegen

Via de Add() methode kan je elementen toevoegen aan de lijst. Je dient als parameter aan de methode mee te geven wat je aan de lijst wenst toe te voegen. Deze parameter moet uiteraard van het type zijn dat de List verwacht.

In volgende voorbeeld maken we een List aan die objecten van het type string mag bevatten en vervolgens plaatsen we er twee elementen in.

Elementen indexeren

Het leuke van een List is dat je deze ook kan gebruiken als een gewone array, waarbij je met de indexer elementen kan aanspreken. Stel bijvoorbeeld dat we een lijst hebben met minstens 4 strings in. Volgende code toont hoe we de string op positie 3 kunnen uitlezen en hoe we die op positie 2 overschrijven:

Ook de klassieke werking met for blijft gelden. De enige aanpassing is dat List<T> niet met Length werkt maar met Count.

Wat kan een List nog?

Interessante methoden en properties voorts zijn:

  • Clear() :methode die de volledige lijst leegmaakt

  • Insert(): methode om element op specifieke plaats in lijst toe te voegen, bijvoorbeeld:

    voegt de string toe op de tweede plek en schuift de rest naar achter

  • Contains(): geef als parameter een specifiek object mee (van het type T dat de List<T> bevat) om te weten te komen of dat specifieke object in de List<> terug te vinden is. Indien ja dan zal true worden teruggeven.

  • IndexOf(): geeft de index terug van het element item in de rij. Indien deze niet in de lijst aanwezig is dan wordt -1 teruggegeven.

  • RemoveAt(): verwijder een element op de index die je als parameter meegeeft.

  • Remove(): verwijder het gegeven element

Contains, Remove en IndexOf zullen zich met jouw eigen klassen niet noodzakelijk gedragen zoals je verwacht. De verklaring hierachter komt later aan bod, wanneer we Equals en GetHashCode bekijken. Ze zullen wel werken zoals verwacht voor voorgedefinieerde types, inclusief DateTime.

Foreach loops

Je kan met een eenvoudige for of while-loop over een lijst itereren, maar het gebruik van een foreach-loop is toch handiger.

Dit is dan ook de meestgebruikte operatie om eenvoudig en snel een bepaald stuk code toe te passen op ieder element van de lijst:

Er bestaat strikt gesproken geen meerdimensionale List<T>, maar een lijst kan wel andere lijsten bevatten. Je kan de functionaliteit ervan dus nabootsen.

Random

Random getallen genereren

Random getallen genereren in je code kan nuttig zijn om verschillende scenario's te simuleren, om software te beveiligen of om de gebruiker een interactievere ervaring te geven.

Random generator

De Random-klasse laat je toe om eenvoudig willekeurige gehele en komma-getallen te maken. Je moet hiervoor twee zaken doen:

  1. Maak eenmalig een Random-generator aan: Random randomgen = new Random(); (wat dit juist wil zeggen zien we in Semester 2).

  2. Roep de Next methode aan telkens je een nieuw getal nodig hebt, bijvoorbeeld: int mijnGetal = randomgen.Next(); De aanroep van de methode Next() zal een geheel getal willekeurig genereren.

De eerste stap dien je dus maar één keer te doen. Vanaf dan kan je telkens aan de generator een nieuw getal vragen m.b.v. Next.

Volgende code toont bijvoorbeeld 3 random getallen op het scherm:

Next mogelijkheden

Je kan de Next methode ook 2 waarden meegeven, namelijk de grenzen waarbinnen het getal moet gegenereerd worden. De tweede waarde wordt nooit gegenereerd. Wil je dus een getal tot en met 10 dan schrijf je 11, niet 10.

Enkele voorbeelden:

Genereer kommagetallen met NextDouble

Met de NextDouble methode kan je kommagetallen genereren tussen 0.0 en 1.0 (1.0 zal niet gegenereerd worden).

Wil je een groter kommagetal dan zal je dit gegenereerde getal moeten vermenigvuldigen naar de range die je nodig hebt. Stel dat je een getal tussen 0.0 en 10.0 nodig hebt, dan schrijf je:

Defaultwaarden

Standaard weigert de compiler het gebruik van variabelen die niet geïnitaliseerd zijn. Probeer volgende code maar eens in een testprogramma:

Je krijgt een foutmelding en je programma voert niet uit. Lees de tekst van de foutmelding. Er wordt uitgelegd dat je naam probeert te gebruiken zonder er een waarde aan te geven. Een variabele moet dus geïnitialiseerd zijn.

We hebben eerder gezien dat elementen van een array ongeveer gebruikt kunnen worden als variabelen, bijvoorbeeld:

Maar hier is een aandachtspuntje: getallen[1], getallen[2], enzovoort zijn nog steeds niet geïnitialiseerd. Anderzijds is de grotere structuur getallen wel geïnitialiseerd. Kunnen we ze dan wel al gebruiken?

Het antwoord is ja. De echte variabele getallen is geïnitialiseerd. Je kan dit uittesten en er zal meteen its opvallen:

Alle niet-ingevulde getallen hebben een defaultwaarde gekregen, namelijk 0.

In het algemeen krijgen niet-geïnitialiseerde waarden een defaultwaarde. Welke waarde dat is, hangt af van hun type. Er bestaan verschillende categorieën van types die deze waarde vastleggen, maar voorlopig mag je het volgende onthouden:

null is een speciale waarde die eigenlijk betekent "er is geen waarde". Van een ontbrekende waarde kunnen we geen eigenschappen of methoden opvragen. Daarom crasht volgende code, die identiek gestructureerd is aan het vorige voorbeeld:

Het probleem is dat we null.ToString() oproepen. Let dus op: je mag geen eigenschap of methode gebruiken door een punt achter een waarde te zetten die null bevat. Je kan hier op controleren met is null:

Code die in beide gevallen moet uitvoeren, zetten we achter het rode bolletje.

Schrijf een programma een getal n ontbindt in . Factoren zijn de getallen waardoor je n kan delen zonder rest (van bijvoorbeeld het getal 100 zijn de factoren 1,2,4,5,10,20,25,50,100.

...
...
"step 2" betekent dat we na elke cyclus de teller met 2 verhogen
Een while wordt aangegeven door een booleaanse expressie waaruit een True-tak vertrekt die er daarna weer in terugkomt. Hier: de oranje zeshoek.
Een do-while wordt aangegeven door een booleaanse expressie die na een reeks instructies wordt geëvalueerd en waarbij de True-tak terug tot diezelfde reeks instructies leidt.

Let er op dat de van variabelen bij loops zeer belangrijk is. Indien je een variabele binnen de loop definieert dan zal deze steeds terug "verdwijnen" wanneer de cyclus van de loop is afgewerkt. Latere declaraties voor variabelen met dezelfde naam hebben niets meer te maken met de oorspronkelijke variabele.

Scope is al in het algemeen behandeld, maar zeker in loops worden er veel fouten tegen gemaakt. Daarom beklemtonen we het hier nog eens.

Flowgorithm programma, gepauzeerd middenin een for-lus, met weergave van de teller.
breakpoint
step over: "voer de instructie als één geheel uit"
step into: "bekijk de instructie zelf als een reeks kleinere instructies"
Onderaan staan de methodes die al het langst aan het uitvoeren zijn. Met andere woorden: Main heeft MethodeA opgeroepen.

Volgende sectie is grotendeels gebaseerd op het volgende .

type(s)
defaultwaarde
factoren
scope
eerder
type[] arraynaam; // type vervang je door het type in kwestie
string[] items;
string[] items;
items = new string[] {"brood", "koffie", "fruit", "thee", "yoghurt"};
string[] items = {"brood", "koffie", "fruit", "thee", "yoghurt"};
string item1, item2, item3;
Console.WriteLine("Wat is het 1e item dat je nodig hebt?");
item1 = Console.ReadLine();
Console.WriteLine("Wat is het 2e item dat je nodig hebt?");
item2 = Console.ReadLine();
Console.WriteLine("Wat is het 3e item dat je nodig hebt?");
item3 = Console.ReadLine();
// hier nog wat code om deze items te tonen
// of om te markeren dat ze in de kar geplaatst zijn
Console.WriteLine("Hoe veel items ga je kopen?");
int aantalItems = Convert.ToInt32(Console.ReadLine());
string lijstje = "";
for (int i = 1; i <= aantalItems; i++) {
    Console.WriteLine($"Wat is het {i}e item dat je nodig hebt?");
    lijstje = $"{lijstje} + {Console.ReadLine()}";
}
string[] items; // een reeks boodschappen voorgesteld als tekst
double[] metingen; // metingen van de neerslag als kommagetallen
decimal[] aandeelWaarden; // kommagetallen met hoge precisie
items = new string[10]; // ruimte om 10 items op het lijstje bij te houden
metingen = new double[365]; // ruimte om een jaar aan metingen te voorzien
aandelen = new decimal[365*10]; // ruimte om de koers over (ongeveer) 10 jaar bij te houden
items[0] = "brood"; // het eerste element heeft index 0
items[1] = "thee";
items[2] = "fruit";
// nog wat boodschappen
items[9] = "krant";
// items[10] kan je niet gebruiken want de laatste index is 10-1
Console.WriteLine(); // geef 10 items die je nodig hebt
items[0] = Console.ReadLine(); // het eerste element heeft index 0
items[1] = Console.ReadLine();
items[2] = Console.ReadLine();
// nog wat boodschappen
items[9] = Console.ReadLine();
// items[10] kan je niet gebruiken want de laatste index is 10-1
Console.WriteLine(); // geef 10 items die je nodig hebt
item1 = Console.ReadLine(); // het eerste element heeft index 0
item2 = Console.ReadLine();
item3 = Console.ReadLine();
// nog wat boodschappen
item10 = Console.ReadLine();
Console.WriteLine(); // geef 10 items die je nodig hebt
for(int i = 0; i <= 9; i++) {
    items[i] = Console.ReadLine();
}
Console.WriteLine($"Het 1e item op je lijstje is {items[0]}");
Console.WriteLine($"Het 2e item op je lijstje is {items[1]}");
// herhaald
Console.WriteLine($"Het 10e item op je lijstje is {items[9]}");
for(int i = 0; i <= 9; i++) {
    Console.WriteLine($"Het {i+1}e item op je lijstje is {items[i]}");
}
string[] stukkenTekst;
Console.WriteLine(stukkenTekst[0].Length);
string[] stukkenTekst;
Console.WriteLine(stukkenTekst[0].ToUpper());
int[][] arrayVanArrays;
Console.WriteLine(arrayVanArrays.Length);
string[] items = new string[10];
Console.WriteLine("Geef de 10 items die je wil kopen:");
for(int i = 0; i < items.Length; i++) {
    items[i] = Console.ReadLine();
}
using System;
using System.Linq; // de methoden zijn afkomstig uit deze namespace
int[] leeftijden = {4, 9, 12, 38, 13, 7};
int oudsteleeftijd = leeftijden.Max();
string[] items = {"brood","koffie","fruit"};
Array.Sort(items);

// Toon resultaat van sorteren
// De items zullen alfabetisch getoond worden
for (int i = 0; i < items.Length; i++)
{
    Console.WriteLine(items[i]);
}
Array.Reverse(items);
Array.Sort(items);
Array.BinarySearch(items, "koffie");
string[] items = {"brood","koffie","fruit"};

Console.WriteLine("Wil je weten of iets al op je lijstje staat? Geef de naam van het item.");
string item = Console.ReadLine();
int index = Array.IndexOf(items,item);
if(index >= 0) {
    Console.WriteLine($"{item} staat op het lijstje met index {index}");
}
else {
    Console.WriteLine("staat nog niet op het lijstje");
}
string[] producten = { "appel","peer","citroen" };
decimal[] prijzen = { 0.65d, 0.35d, 0.70d };
Console.WriteLine("Van welk product wil je de prijs weten?");
string product = Console.ReadLine();
string[] producten = {"appel", "peer", "citroen"};
decimal[] prijzen = { 0.65m, 0.35m, 0.70m };
System.Console.WriteLine("Van welk product wil je de prijs kennen?");
string product = Console.ReadLine();
int productIndex = Array.IndexOf(producten,product);
if (productIndex < 0) {
    System.Console.WriteLine("Het product is niet aanwezig.");
}
else {
    System.Console.WriteLine(prijzen[productIndex]);
}
public static void Macht(int grondtal, int macht)
{
    int resultaat = grondtal;
    for (int i = 1; i < macht; i++)
    {
        resultaat *= grondtal;
    }
    Console.WriteLine($"De {macht}e macht van {grondtal} is {resultaat}");
}
public static void Main() {
    Console.WriteLine("Welk grondtal wil je gebruiken?");
    int grond = Convert.ToInt32(Console.ReadLine());
    Console.WriteLine("Tot welke macht wil je verheffen?");
    int gewensteMacht = Convert.ToInt32(Console.ReadLine());
    Macht(grond,gewensteMacht);
}
int a = 3;
int b = a;
b = b+1;
Console.WriteLine(a);
Console.WriteLine(b);
public static void VeranderGetal(int getal) {
  getal = getal - 1;
  Console.WriteLine($"Het getal is {getal}");
}

public static void Main() {
  int mijnGetal = 4;
  Console.WriteLine($"Het getal is {mijnGetal}");
  VeranderGetal(mijnGetal);
  Console.WriteLine($"Het getal is {mijnGetal}");
}
public static void VeranderGetal(int getal) {
  getal = getal - 1;
  Console.WriteLine($"Het getal is {getal}");
}

public static void Main() {
  int getal = 4;
  Console.WriteLine($"Het getal is {getal}");
  VeranderGetal(getal);
  Console.WriteLine($"Het getal is {getal}");
}
 static void PrintOrderDetails(string sellerName, int orderNum, string productName)
 {
     Console.WriteLine($"Verkoper: {sellerName}");
     Console.WriteLine($"Ordernummer: {orderNum}");
     Console.WriteLine($"Product: {productName}");
 }
PrintOrderDetails("Gift Shop", 31, "Red Mug");
 PrintOrderDetails(orderNum: 31, productName: "Red Mug", sellerName: "Gift Shop");
 PrintOrderDetails(productName: "Red Mug", sellerName: "Gift Shop", orderNum: 31);
PrintOrderDetails("Gift Shop", 31, productName: "Red Mug");
PrintOrderDetails(sellerName: "Gift Shop", 31, productName: "Red Mug");    // C# 7.2 onwards
PrintOrderDetails("Gift Shop", orderNum: 31, "Red Mug");
PrintOrderDetails(productName: "Red Mug", 31, "Gift Shop");
PrintOrderDetails(31, sellerName: "Gift Shop", "Red Mug");
PrintOrderDetails(31, "Red Mug", sellerName: "Gift Shop");
static double ComputePrice(int numberOfItems, double itemPrice, double discountPercentage = 0, bool doubleDiscount = false) {
    if (not doubleDiscount) {
        return (numberOfItems * itemPrice) * (1.0 - discountPercentage / 100);
    }
    else {
        return (numberOfItems * itemPrice) * (1.0 - 2 * discountPercentage / 100);
    }
}
Console.WriteLine(ComputePrice(4,10.0)); // klassieke aanroep
Console.WriteLine(ComputePrice(4,10.0,5.0)); // 5% korting op de totaalprijs
Console.WriteLine(ComputePrice(4,10.0,5.0,true)); // 10% korting op de totaalprijs omdat de korting verdubbeld wordt
Console.WriteLine(ComputePrice(4,10.0,true)); // derde argument moet een double zijn!
// dit heeft weinig zin want we verdubbelen 0% korting, maar voor C# werkt dit wel
Console.WriteLine(ComputePrice(4,10.0,doubleDiscount: true));
static int ComputeArea(int lengte, int breedte)
{
    int opp = lengte*breedte;
    return opp;
}

static int ComputeArea(int radius)
{
    int opp = (int)(Math.PI*radius*radius);
    return opp;
}
Console.WriteLine($"Rechthoek: {ComputeArea(5, 6)}");
Console.WriteLine($"Circle: {ComputeArea(7)}");
public static void MethodeNaam()
{
    // code die een bepaalde taak uitvoert
}
MethodeNaam();
class Program {
    public static void ToonGroen() {
        Console.WriteLine("Groen");
    }
    public static void ToonBlauw() {
        Console.WriteLine("Blauw");
    }
    public static void Main() {
        ToonGroen();
    }
}
Hoe hoog is de driehoek?
> 4
Welk karakter gebruiken we als patroon?
> #
#
##
###
####
Hoe hoog is de driehoek?
> 3
Welk karakter gebruiken we als patroon?
> .
.
..
...
List<String> myStringList = new List<String>();
myStringList.Add("This is the first item in my list!");
myStringList.Add("And another one!");
Console.WriteLine(myStringList[3]);
myStringList[2] = "andere zin";`
for(int i = 0 ; i < myStringList.Count; i++)
{
    Console.WriteLine(myStringList[i])
}
myStringList.Insert(1,"A fourth sentence");
List<int> integerList=new List<int>();
integerList.Add(2);
integerList.Add(3);
integerList.Add(7);

foreach(int prime in integerList)
{
   Console.WriteLine(prime);
}
Random mygen = new Random();
int getal1 = mygen.Next();
int getal2 = mygen.Next();
int getal3 = mygen.Next();
Console.WriteLine(getal1);
Console.WriteLine(getal2);
Console.WriteLine(getal3);
Random somegenerator = new Random();

int a = somegenerator.Next(0,10);  //getal tussen 0 tot en met 9
int b = somegenerator.Next(5,101);  //getal tussen 5 tot en met 100
int c = somegenerator.Next(0,b);  //getal tussen 0 tot en met het getal dat de lijn ervoor werd gegenereerd.
Random myran = new Random();
double randomgetal= myran.NextDouble() * 10.0;
string naam;
Console.WriteLine(naam);
int[] getallen = new int[10];
getallen[0] = 5;
int[] getallen = new int[10];
getallen[0] = 5;
for (int i = 0; i < getallen.Length; i++) {
	// ToString heeft hier geen effect, maar we tonen verderop iets
	Console.WriteLine(getallen[i].ToString());
}

elk getaltype

0

bool

false

string

null

elk type array

null

Random

null

string[] teksten = new string[10];
teksten[0] = "hallo wereld";
for (int i = 0; i < teksten.Length; i++) {
	Console.WriteLine(teksten[i].ToString());
}
string[] teksten = new string[10];
teksten[0] = "hallo wereld";
for (int i = 0; i < teksten.Length; i++) {
	if (!(teksten[i] is null)) {
	  Console.WriteLine(teksten[i].ToString());
	}
}
2KB
boodschappenlijstje-met-array.fprg
1KB
taart.fprg
1KB
controlflowmethoden.fprg
3KB
H07-EmailGeneratorTemplate.fprg

H8: Numerieke data

In dit hoofdstuk kijken we naar meer gevorderde operaties die vooral te maken hebben met getallen.

H9: Meerdimensionaal werken

De Math klasse

Berekeningen

Een groot deel van je leven als ontwikkelaar zal bestaan uit het bewerken van variabelen in code. Meestal zullen die bewerkingen de vorm aannemen van berekeningen. De Math klasse zal ons hier bij kunnen helpen.

De Math klasse

De Math klasse bevat aardig wat handige methoden. Deze klasse bevat methoden voor een groot aantal typische wiskundige methoden (sinus, cosinus, vierkantswortel, macht, afronden, etc.) en kan je dus helpen om leesbaardere expressies te schrijven.

Stel dat je de derde macht van een variabel getal wenst te berekenen. Zonder de Math-klasse zou dat er zo uitzien:

double result = getal*getal*getal;

Met de klasse kunnen we schrijven:

double result = Math.Pow(getal, 3);

Het voordeel is dat je dit makkelijk kan aanpassen naar een hogere macht.

De Math klasse ontdekken

Als je in Visual Studio Code Math schrijft, gevolgd door een punt . krijg je alles te zien wat de Math-klasse kan doen.

Methoden gebruiken

  1. Schrijf de Methode zonder argumenten. Bijvoorbeeld Math.Pow() (je mag de rode error negeren).

  2. Je krijgt nu de help-files te zien van deze methode op MDSDN.

  3. Klik desnoods op de pijltjes voor de verschillende versies van deze methode.

PI (π)

Ook het getal Pi (3.141...) is beschikbaar in de Math klasse. Dit is geen methode, maar een gewone waarde. Het woordje const betekent dat je deze waarde niet kan aanpassen.

Je kan deze als volgt gebruiken in berekeningen:

double straal = 5.5;
double omtrek = Math.PI * 2 * straal;

Klassiek afronden

"Klassiek" afronden is afronden tot het dichtstbijzijnde getal. Dit doe je met Math.Round. Deze methode heeft twee parameters: het getal dat je wil afronden en het aantal cijfers na de komma dat je wil bewaren. Let hierbij goed op: dit berekent een nieuwe waarde (van type double) met de gewenste precisie, maar zorgt er niet automatisch voor dat deze waarde ook met dat aantal cijfers getoond wordt. Anders gezegd: Math.Round(12.0, 2) kan exact voorgesteld worden met hooguit twee cijfers na de komma, maar wordt standaard niet getoond met twee cijfers na de komma. Dat laatste behandelen we verder bij stringformattering.

Afronden naar boven of beneden

Naast "klassiek" afronden kan je ook zuiver in één richting afronden. Dan krijg je het dichtstbij gelegen gehele getal waarvoor geldt benadering =< getal (bij naar beneden afronden) of benadering >= getal (bij naar boven afronden). Dit doen we met Math.Floor en Math.Ceiling. Bijvoorbeeld: Math.Floor(12.6) is 12.0, ook al is 13 een benadering die dichter bij ligt. Dat komt omdat 12 =< 12.6, terwijl 13 > 12.6.

Kennisclip
Kennisclip
Kennisclip
Kennisclip
artikel
Kennisclip
Kennisclip
Kennisclip voor deze inhoud

Casting en conversie

Casting en conversie

Wanneer je de waarde van een variabele wil toekennen aan een variabele van een ander type mag dit niet zomaar. Volgende code zal bijvoorbeeld een error geven, omdat aan de linkerkant staat dat je een geheel getal wil bijhouden (door middel van int) en aan de rechterkant een kommagetal staat:

int age = 4.3;

Een kommagetal past niet in het geheugen voorzien voor een geheel getal: als er toch een zinvolle omzetting mogelijk is, zal je moeten converteren of casten.

Dit kan op o.a. volgende manieren:

  • Via casting: de (klassieke) manier die ook werkt in veel andere programmeertalen.

  • Via de Convert klasse. Deze staat omzettingen toe die handig zijn, maar niet op het niveau van de taal zijn vastgelegd.

Casting

Een klassieke, in veel talen voorziene manier om date om te zetten is casten. Hierbij dien je aan de compiler te zeggen: "Volgende variabele die van het type x is, moet aan deze variabele van het type y toegekend worden. Ik besef dat hierbij data verloren kan gaan, maar zet de variabele toch maar om naar het nieuwe type, ik draag alle verantwoordelijkheid voor het verlies." Je kan bijvoorbeeld bovenstaand kommagetal enkel omzetten naar een geheel getal als je de cijfers na de komma verloren laat gaan. In het Engels betekent to cast "in een vorm gieten".

Wat is casting

Casting heb je nodig om een waarde van een bepaald type om te zetten naar een waarde van een ander type. Stel dat je een complexe berekening hebt waar je werkt met verschillende types (bijvoorbeeld int, double en float). Door te casten kan je het soort bewerkingen sturen. Je gaat namelijk bepaalde types even als andere types gebruiken.

Het is belangrijk in te zien dat het casten van een variabele naar een ander type enkel een gevolg heeft tijdens het uitwerken van de expressie waarbinnen je werkt. Het casten op zich verandert niets aan de variabele of waarde zelf.

Casting duid je aan door voor de variabele of literal het datatype tussen haakjes te plaatsen naar wat het omgezet moet worden:

int mijngetal = (int) 3.5;
Console.WriteLine(mijngetal);

of

double kommagetal = 13.8;
int kommaNietWelkom = (int) kommagetal;
Console.WriteLine(kommaNietWelkom);

Narrowing

Casting gebruik je vaak als je een variabele wil gebruiken als een ander type dat deze waarde niet kan bevatten. We moeten dan aan narrowing doen, letterlijk het versmallen van de data.

Bekijk eens het volgende voorbeeld:

double var1;
int var2;

var1 = 20.4;
var2 = var1;

Dit zal niet gaan. Je probeert namelijk een waarde van het type double in een variabele van het type int te steken. Dat gaat enkel als je informatie weggooit. Je moet aan narrowing doen.

Dit gaat enkel als je expliciet aan de compiler zegt: het is goed, je mag informatie weggooien, ik begrijp dat en zal er rekening mee houden. Dit noemen we expliciete casting.

En je doet dit door voor de variabele die dienst moet doen als een ander type, het nieuwe type, tussen ronde haakjes te typen, als volgt:

double var1;
int var2;

var1 = 20.4;
var2 = (int) var1;

Het resultaat in var2 zal 20 zijn (alles na de komma wordt bij casting van een double naar een int weggegooid).

Merk op dat var1 nooit van datatype is veranderd; enkel de inhoud ervan (20.4) werd eruit gehaald, omgezet ("gecast") naar 20 en dan aan var2 toegewezen dat enkel int aanvaardt.

Widening

Casting kan je ook gebruiken als je aan widening doet (een kleiner type in een groter type steken), als volgt:

int var1;
double var2;

var1 = 20;
var2 = var1;

Deze code zal zonder problemen gaan. var2 zal de waarde 20.0 bevatten. De inhoud van var1 wordt verbreed naar een double, eenvoudigweg door er een kommagetal van te maken. Er gaat geen inhoud verloren. Dit wordt ook impliciete casting genoemd. Er bestaan scenario's waarin je dit toch expliciet wil doen, maar die komen minder vaak voor en zullen we benoemen als we ze tegenkomen.

Conversie

Casting is een in de taal ingebakken manier van data omzetten, die vooral zeer nuttig is daar deze ook werkt in andere C#-related programmeertalen zoals C, C++ en Java. Om te weten hoe deze omzettingen gebeuren, kan je kijken in de handleiding van de taal C# zelf.

Echter, .NET heeft ook methoden die je kunnen helpen om data van het ene type naar het andere te brengen. Deze methoden zitten binnen de Convert-klasse.

Het gebruik hiervan is zeer eenvoudig. Enkele voorbeelden:

int getal= Convert.ToInt32(3.2); //double to int
double anderGetal= Convert.ToDouble(5); //int to double
bool isWaar= Convert.ToBoolean(1); //int to bool
int userAge= Convert.ToInt32("19"); //string to int
int ageOther= Convert.ToInt32(anderGetal); //double to int

Je plaatst tussen de ronde haakjes de variabele of literal die je wenst te converteren naar een ander type. Merk op dat naar een int converteren met .ToInt32() moet gebeuren. Om naar een short te converteren is dit met behulp van .ToInt16(). Het voordeel van deze werkwijze is dat ze vastgelegd kan worden door de programmeur en dat ze vaak meer "intuïtieve" omzettingen doet dan een cast. En, als de omzettingen je niet bevallen, kan je een eigen klasse MyConvert schrijven die de omzettingen doet zoals je zelf wil. Het zijn namelijk gewoon methodes.

Return waarden

Een stappenplan, en dus een methode, voer je uit met het oog op een bepaald resultaat. Soms moet dat resultaat terugbezorgd worden aan een opdrachtgever. Soms niet. Methodes staan toe beide variaties op een stappenplan te schrijven.

We gebruiken opnieuw een proces uit het echte leven om de interactie te omschrijven voor we de sprong maken naar code.

Voorbeeld uit het echte leven

Denk aan een ouderwetse bakker. Het stappenplan dat deze bakker volgt is er een om brood te maken. Om brood te maken heb je bloem nodig. Bloem maken is niet echt een onderdeel van het takenpakket van de bakker. Hij laat de molenaar dit voor hem doen. Hij vraagt niet gewoon aan de molenaar om bloem te maken, maar ook om deze aan hem te bezorgen zodat hij er zelf mee verder kan werken. De bakker is niet geïnteresseerd in hoe de molenaar bloem maakt, maar hij heeft het eindproduct nodig om zijn brood af te maken.

We vertalen nu deze interactie naar code en verklaren daarna de vertaling:

Dit is maar een voorbeeld om de flow te verduidelijken. Er is geen "juiste" manier om een methode te schrijven om brood te bakken (tenzij je misschien een broodmachine programmeert).

public static void Bakker() {
  // we stellen elk ingrediënt voor als string
  string ingredient1 = "water";
  string ingredient2 = "gist";
  string ingredient3 = Molenaar();
  Console.WriteLine($"Ik maak brood met {ingredient1}, {ingredient2} en {ingredient3}");
}

public static string Molenaar() {
  return "bloem";
}

Hier vallen twee zaken op: in de signatuur (d.w.z. alles van het begin van de regel tot en met het gesloten ronde haakje) van de methode Molenaar staat niet void, maar string. Dit komt omdat deze methode een resultaat teruggeeft aan de opdrachtgever en dat resultaat van het type string is. Daarnaast heb je het sleutelwoord return, gevolgd door de tekst "bloem". Het woordje return betekent: "bezorg dit aan de code die deze methode heeft opgeroepen". Anders gezegd: dit is hoe de molenaar de geproduceerde bloem overhandigt aan zijn opdrachtgever, de bakker. Waar je de call Molenaar() schrijft komt tijdens de uitvoering het geproduceerde resultaat dankzij die return.

Het return type

Je moet dus noteren wat voor resultaat er achter return staat: een string of eventueel iets anders. Als er een string volgt na return, zeggen we dat string het return type is van die methode. Als een methode een geheel getal produceert, heeft ze het return type int of een verwant getaltype.

Maar niet alle methodes bezorgen iets terug aan hun opdrachtgever. Sommige hebben gewoon een effect. Voor deze methodes schrijven we void als return type. Als een methode dit return type heeft, kunnen we het resultaat van een call dus niet toekennen aan een variabele, want er is geen resultaat.

"Er is geen resultaat" wil niet zeggen dat een void methode niets doet. Console.WriteLine is bijvoorbeeld een methode met return type void. Het punt is dat de call je niets terugbezorgt waarmee je verder kan werken. Je kan bijvoorbeeld niet schrijven: string tekst = Console.WriteLine("Dit is tekst");

Er is dus een groot verschil tussen return "tekst"; en Console.WriteLine("tekst"); Bij de eerste code is er geen garantie dat de geproduceerde tekst ooit op het scherm verschijnt, maar je kan er wel mee verder werken. Bij de tweede verschijnt hij per definitie wel op het scherm, maar kan je hem niet toekennen aan een variabele om later mee verder te werken.

We kunnen dus wel doen: string ingredient3 = Molenaar(); (want het return type van Molenaar() is string) maar we kunnen niet schrijven: string product = Bakker(); (want Bakker() heeft return type void en produceert dus geen resultaat). Deze code compileert dan ook niet.

Gekend voorbeeld van een vaak gemaakte fout

Je kent de methode Replace van strings. Je gebruikt deze als volgt:

string ingevoerdeTekst = Console.ReadLine();
string tekstZonderSpaties = ingevoerdeTekst.Replace(" ","");
Console.WriteLine($"Jouw tekst zonder spaties is: {tekstZonderSpaties}");

Veel beginners doen het volgende:

string ingevoerdeTekst = Console.ReadLine();
ingevoerdeTekst.Replace(" ","");
Console.WriteLine($"Jouw tekst zonder spaties is: {ingevoerdeTekst}");

Dit is fout. Replace berekent het resultaat van de aanpassing en geeft dat terug aan de opdrachtgever. Het past de ingevoerde tekst niet aan. In de code van Replace staat dus ook een return!

Parameters en returnwaarden samen

Als je parameters en returnwaarden combineert, worden methoden erg flexibel. Dan kan je al echte bouwsteentjes van een programma gaan schrijven.

Een voorbeeld: de stelling van Pythagoras vertelt ons hoe lang de schuine zijde van een driehoek met twee rechte hoeken is:

Als we deze berekening regelmatig uitvoeren voor verschillende waarden van a en b, kunnen we ze als volgt in een methode gieten:

public static double Pythagoras(double a, double b) {
  double som = a * a + b * b;
  return Math.Sqrt(som);
}

Nu kunnen we heel snel allerlei schuine zijdes uitrekenen, bv.:

public static void Main() {
  Console.WriteLine($"De schuine zijde van een driehoek met benen van 4cm en 3cm is {Pythagoras(4,3)}cm");
  Console.WriteLine($"De schuine zijde van een driehoek met benen van 12cm en 2cm is {Pythagoras(12,2)}cm");
}

Bovendien maken we gebruik van het feit dat de methode Sqrt (square root is vierkantswortel) ook een parameter heeft en een resultaat teruggeeft. Eigenlijk verloopt een uitvoering dus zo:

  1. de methode Main start op

  2. we willen bepaalde tekst uitprinten, maar we moeten die eerst nog bepalen met de methode Pythagoras

  3. de methode Pythagoras start op met de argumenten 4 en 3

  4. de methode berekent de som van de kwadraten en noemt deze som

  5. de methode maakt zelf gebruik van nog een methode, Sqrt, en geeft daarbij som als argument

  6. Sqrt rekent de vierkantswortel uit en geeft deze terug aan Pythagoras

  7. Pythagoras geeft deze verder door aan Main

  8. de zin wordt correct afgeprint

Meestal is het dus een goed idee zo weinig mogelijk informatie te communiceren met WriteLine en zo veel mogelijk met return te werken. Meerbepaald: gebruik WriteLine wanneer je de uiteindelijke informatie in haar uiteindelijke vorm hebt en deze echt aan de gebruiker moet tonen. Gebruik return wanneer je het resultaat van een deeltaak aan een opdrachtgever wil bezorgen.

Volgend Flowgorithm toont aan hoe je dankzij methoden een probleem kan opbreken in meerdere kleine stappen, waarbij je informatie doorgeeft via parameters en returnwaarden:

2KB
pythagoras-uitgebreid.fprg

Oefeningen

Oefeningen

Oefening: H6-ArrayTrueFalse

Leerdoelen

  • Declareren van arrays

  • Initialiseren van arrays

  • Opvullen van arrays

  • Arrays gebbruiken - afprinten

Functionele analyse

Maak een array gevuld met afwisselend true en false (de array is 30 lang)

Per array: output de array naar het scherm, maar ieder element naast elkaar met komma gescheiden. Dus niet:

1 true

2 false

3 true

4 \\etc

maar wel: true, false, true, ...

Implementeer onderstaande flowchart in C#.

Technische analyse

2KB
H6-ArrayTrueFalse.fprg

Voorbeeldinteractie

Oefening: H6-ArrayOefener1

Leerdoelen

  • Declareren van arrays

  • Initialiseren van arrays

  • Opvullen van arrays

  • Arrays gebruiken

Functionele analyse

Maak een programma dat aan de gebruiker vraagt om 10 waarden (int) in te voeren in een array. Vervolgens toont het programma de som, het gemiddelde en het grootste getal van deze 10.

Vervolgens vraagt het programma de gebruiker om een getal in te voeren. Het programma toont dan alle getallen die groter of gelijk zijn aan dit ingevoerde getal zijn die in de array aanwezig zijn. Indien geen getallen groter zijn dan verschijnt een bericht Niets is groter op het scherm.

Implementeer onderstaande flowchart in C#. Merk op: je kan in principe de statistieken al bijhouden terwijl de getallen worden ingevoerd, maar hier worden ze pas achteraf berekend. Doe het ook zo.

Technische analyse

3KB
H6-ArrayOefener1.fprg

Voorbeeldinteractie

Oefening: H6-Boodschappenlijst

Leerdoelen

  • Declareren van arrays

  • Initialiseren van arrays

  • Opvullen van arrays

  • Arrays gebruiken

Functionele analyse

Maak een programma dat de gebruiker een boodschappenlijstje laat samenstellen.

  • Het programma vraagt eerst hoeveel items de boodschappenlijst moet bevatten en laat dan de lijst vullen.

  • Vervolgens wordt een gesorteerde lijst van de items getoond.

  • Daarna, in de winkel, kan de gebruiker aangeven welke items er gekocht worden. De gebruiker kan dit blijven doen zolang er 'ja' geantwoord wordt op de vraag 'Nog winkelen?'. Als de gebruiker een item intypt dat niet op de lijst staat, wordt er een bericht getoond.

  • Na het winkelen toont het programma welke items van de lijst niet gekocht zijn.

Implementeer onderstaande flowchart in C#.

Technische analyse

8KB
H6-Boodschappenlijst.fprg

Voorbeeldinteractie

Oefening: H6-Kerstinkopen

Leerdoelen

  • Declareren van arrays

  • Initialiseren van arrays

  • Opvullen van arrays

  • Arrays gebruiken

  • Array methodes gebruiken

Functionele analyse

Maak een programma dat kan gebruikt worden om kerstinkopen te doen, rekening houdend met een budget. Na de inkopen, wordt het totaal gespendeerd bedrag getoond en het hoogste, laagste en gemiddelde bedrag.

Technische analyse

Probeer zelf een flowchart te maken en zet die dan om naar C#.

Voorbeeldinteractie

Oefening: H6-Lotto

Leerdoelen

  • Arrays in flowcharts gebruiken

  • Declareren van arrays

  • Initialiseren van arrays

  • Opvullen van arrays

  • Opzoeken waarde in arrays

Functionele analyse

Je vraagt de gebruiker zijn 6 lotto getallen (getal tussen 1 en 42) in te geven. Je hoeft geen controle te doen op de getallen die de gebruiker ingeeft. Je bewaart deze getallen in een array lottoFormulier.

Vervolgens simuleer je een trekking. Dat doe je door random 6 getallen te genereren (zie hiervoor Random) en die ook in een array lottoTrekking te bewaren. Let op: hier moet je wel controleren of het inderdaad 6 verschillende getallen zijn (hoe kan je nagaan of iets aanwezig is in je array?).

Dan doe je een validatie. Heeft de gebruiker 3 juiste cijfers wint hij 10 €, bij 4 juiste cijfers 1000 €, bij 5 juiste cijfers 100.000 € en bij 6 juiste cijfers 10.000.000 €.

Zorg dat alles mooi wordt getoond aan de gebruiker. Lotto getallen in stijgende volgorde, Trekking getallen in stijgende volgorde, winst. Zie voorbeeldinteractie.

Technische analyse

Maak een flowchart en zet die om naar C#

Wat betreft het random genereren van 6 getallen tussen 1 en 42. Daar krijg je het volgend stukje code voor (probeer te begrijpen hoe het werkt en zoek op internet het gebruik van Random eens op):

Random random = new Random();
int lottoGetal;
        
for (int i = 0; i < lottoTrekking.Length; i++) {
    do {
        lottoGetal = random.Next(42) + 1;
    }
    while (Array.IndexOf(lottoTrekking, lottoGetal) >= 0);
    lottoTrekking[i] = lottoGetal;
}

Voorbeeldinteractie

Uitbreiding

Test de ingegeven lotto getallen en zorg er voor dat er geen getallen kleiner dan 1 of groter dan 42 ingegeven kunnen worden en dat er ook geen dubbels voorkomen..

Oefening: H6-IntegerIndexOf

Leerdoelen

  • Arrays in flowcharts gebruiken

  • Opzoeken waarde in integer arrays – zelf geschreven methode

Functionele analyse

Probeer d.m.v. Flowgorithm de flowchart te gebruiken en zet dan om naar C#.

Technische analyse

3KB
H6-IntegerIndexOf.fprg

Voorbeeldinteractie

Oefening: H6-BinarySearch

Leerdoelen

  • Arrays in flowcharts gebruiken

  • De array gebruiken, in een array zoeken.

Functionele analyse

Heel eenvoudig uitgelegd zal het algoritme de te zoeken waarde vergelijken met de middelste waarde (van een gesorteerde array). Als de waarde niet gelijk is wordt dat gedeelte van de array waar de waarde zich niet in kan bevinden weggegooid. De zoektocht gaat verder totdat de waarde is gevonden (of niet gevonden indien alle mogelijkheden zijn doorzocht)

Implementeer onderstaande flowchart in C#. Om twee strings te ordenen, gebruik je string1.CompareTo(string2). Dit levert -1 als string1 voor string2 komt, 1 als string1 na string2 komt en 0 als ze op dezelfde plaats komen.

Technische analyse

4KB
H6-BinarySearch.fprg

Voorbeeldinteractie

Oefening: H6-Boodschappenlijst-list

Leerdoelen

  • Declareren van een List

  • Initialiseren van een List

  • List gebruiken

Functionele analyse

Maak een een alternatieve versie van H6-Boodschappenlijst. Maak ditmaal gebruik van List<T> in plaats van een array.

Technische analyse

  • Je hoeft de grootte van de lijst niet meer op voorhand te vragen.

  • Een lijst myList sorteren doe je door myList.sort() op te roepen.

  • Je mag elementen die gekocht zijn gewoonweg verwijderen uit de lijst.

  • Op het eindetoon je de resterende items met een foreach lus.

  • Je geeft een lege regel in om aan te geven dat je niets wil toevoegen aan je lijstje.

  • Noem je methode BoodschappenList

Voorbeeldinteractie

Wat is item 1 op je lijst?
> kaas
Wat is item 2 op je lijst?
> eieren
Wat is item 3 op je lijst?
> boter
Wat is item 4 op je lijst?
>
Dit is je gesorteerde lijst:
1: boter
2: eieren
3: kaas
Op naar de winkel!
Welk item heb je gekocht?
> kaas
Nog winkelen? (Ja of Nee)
> nee
Naar huis met de boodschappen!
Volgende items van je lijst ben je vergeten te kopen:
boter eieren

Oefening: H6-Kerstinkopen-list

Leerdoelen

  • Declareren van een List

  • Initialiseren van een List

  • List gebruiken

Functionele analyse

Maak een een alternatieve versie van H6-Kerstinkopen. Maak ditmaal gebruik van List<T> in plaats van een array.

Technische analyse

  • Je hoeft niet meer te vragen op voorhand hoe veel cadeautjes er gekocht zullen worden.

  • Je mag stoppen bij een lege regel invoer.

  • Je moet ook stoppen zodra je over budget gaat.

  • Noem je methode KerstinkopenList

Voorbeeldinteractie

Wat is het budget voor je kerstinkopen?
> 500
Prijs van cadeau 1?
> 200
Prijs van cadeau 2?
> 330
Je bent al 30.0 euro over het budget!
Info over je aankopen:
Totaal bedrag: 530.0 euro
Duurste cadeau: 330.0 euro
Goedkoopste cadeau: 200.0 euro
Gemiddelde prijs: 265.0 euro

Intermezzo: TextCell

Inleiding

Het voelt misschien nog niet zo, maar je kent intussen al redelijk veel programmeerconcepten. Je kent onder meer variabelen van verschillende types, beslissingen, loops, arrays en methoden. Nu is het niet alleen kwestie van die zaken te kennen, maar ook van die zaken te gebruiken. Daarom zullen we deze les een beetje anders aanpakken dan gewoonlijk. We zullen een vereenvoudigde versie van een bestaand programma schrijven. Dit is een uitstekende manier om te leren. Het maakt niet uit dat dat programma al bestaat. Het doel is om bij te leren. Door bestaande software na te bouwen, heb je een duidelijk referentiepunt.

De software die wij hier zullen bouwen is een tekstgebaseerde spreadsheet, zoals Excel of Calc. Onze versie zal meer lijken op de allereerste spreadsheets dan op een moderne spreadsheet, maar de essentie zal er wel zijn. De belangrijkste beperkingen zijn:

  • We werken tekstgebaseerd. Anders zouden we tijd moeten spenderen aan het bestuderen van een grafische toolkit. In principe zou je dit later kunnen oplossen door een versie in TypeScript te bouwen die tabellen in de browser gebruikt.

  • We werken ééndimensionaal. We hebben dus een spreadsheet van één rij, in plaats van een spreadsheet met rijen en kolommen. We zullen dit op het einde van het semester rechtzetten, maar we hebben nog wat meer ervaring met arrays nodig om dit vlot te laten verlopen.

  • We ondersteunen alleen optellingen, geen andere berekeningen. Op zich is het systeem hetzelfde voor bijkomende berekeningen, maar je moet alle gevallen analyseren. In de praktijk wordt zoiets gedaan met een parser en dat gaat wat te ver voor deze demonstratie.

Dat zijn serieuze beperkingen, maar vergeet niet dat spreadsheets als tientallen jaren bestaan en ontwikkeld worden door programmeerteams. Ze kunnen het resultaat zijn van letterlijk duizenden jaren werk door professionele programmeurs.

Tijdens deze les moet je zelf de gedemonstreerde code uitschrijven. Ik zal telkens een stapje uitleggen en dan doen we een uitbreiding van het project. Je zal tijdens de labo's en tijdens het examen een paar uitbreidingen doen aan dit project, dus zorg dat je actief nadenkt over de code en niet gewoonweg overtypt. Als dat betekent dat je moet teruggaan naar een eedere les, doe dat dan. Er is geen snelle manier om goed te leren programmeren, maar dit is de minst trage.

Laat me ook even duidelijke verwachtingen scheppen: Er wordt niet verwacht dat je dit project zelf kan bedenken. Er zal in dit vak nooit een oefening zijn die de omvang van dit project benadert. De concepten die we hier gebruiken zijn bijna allemaal al gekende leerstof, maar om dit project te bedenken heb je naast de concepten ook al redelijk wat ervaring nodig. Het is dus volledig normaal als je tijdens dit project denkt: "Dit had ik zelf nooit bedacht." Het is zelfs de bedoeling. Het punt hier is om je te laten zien dat je niet noodzakelijk erg veel concepten nodig hebt om complexere programma's te schrijven. Je kan best veel met de basics.

Hoe werkt een spreadsheet?

  • Een spreadsheet is in essentie een rooster op basis van rijen en kolommen.

  • De combinatie van rij en kolom duidt één "vakje" aan. Zo'n vakje noemen we in technische termen een "cel".

  • Een cel bevat data of een berekening:

    • data is heel vaak een getal, een stuk tekst of een datum

    • een berekening begint met =

      • een berekening mag ingebouwde methodes gebruiken

      • een berekening mag ook verwijzen naar andere cellen

Merk op: kolommen worden aangeduid met letters en rijen met getallen.

Moderne spreadsheets kunnen natuurlijk veel meer, zoals je ook ziet in de interface van Calc, maar leer stappen voor je leert lopen. Dit is al niet zo makkelijk. Je moet dit eerst goed onder de knie hebben voor je probeert meer te doen.

Een nieuwe klasse maken

We zullen de spreadsheet maken in hetzelfde project als onze labo-oefeningen. Dat maakt dat er niet te veel gewisseld moet worden tussen projecten wanneer je hier uitbreidingen voor schrijft tijdens de labosessies. We noemen de klasse TextCell. Dat is de naam die we verder zullen gebruiken voor onze eigen spreadsheet.

Algemene structuur

We kijken eerst naar Calc voor wat inspiratie. Wanneer we opstarten, staat er een lege spreadsheet klaar. Het programma wacht eigenlijk gewoon tot we een cel invullen. Wanneer we dat doen, worden alle cellen met daarin een berekening opnieuw berekend. In Calc kan ik bijvoorbeeld cel B1 aanpassen en dan zie je dat cel B2 ook wijzigt, omdat die een berekening op basis van cel B1 bevat. Daarna kunnen we weer aanpassingen doen.

We zullen dit zo goed mogelijk nabootsen. Om te beginnen zetten we een nieuwe, lege spreadsheet klaar. Hoe doen we dit? Een cel lijkt erg op een variabele, want je kan er letterlijke data of het resultaat van een berekening in plaatsen. Anderzijds kunnen we niet voor elke cel een variabele in onze programmacode zetten. Dan zou onze code gigantisch worden en zou er geen manier zijn om systematisch alle cellen te doorlopen. We waren op hetzelfde probleem gebotst toen we arrays introduceerden. Daar konden we geen variabele voorzien voor elk item op een boodschappenlijstje. We zullen onze cellen dan ook voorstellen door middel van een array.

Dit levert drie belangrijke beperkingen op:

Ten eerste, een array hebben we voorgesteld als een rij of een kolom, maar niet als een combinatie van rijen en kolommen. We zullen ons hier tijdelijk bij neerleggen en een een spreadsheet maken waarvan we alleen de eerste rij kunnen invullen. We zullen dit later nog recht zetten.

Ten tweede, de grootte van een array wordt vastgelegd zodra de array wordt aangemaakt. Ook hier leggen we ons voorlopig bij neer. We zullen bij het opstarten van de spreadsheet vragen hoe groot de array moet zijn. Later zullen we zien dat hier ook rond gewerkt kan worden.

Ten derde, een array bevat één type waarden. Dus strings of getallen of booleans of iets anders, maar geen mengeling. Maar merk op dat je in Calc gewoon iets intypt en dat het systeem er dan zelf een interpretatie aan geeft. Bijvoorbeeld: als ik 01/03/1987 intyp, verschijnt dit als 01/03/87. Dat komt omdat deze tekst er uitziet als een datum. Terwijl Vincent ongewijzigd blijft. Dus op zich kan je gewoon tekst ingeven en zaken die er uitzien als getallen of datums behandelen als getallen of datums wanneer nodig. Met andere woorden, we zullen een string[] gebruiken om ons rooster voor te stellen.

Dus we hebben een rooster (i.e. een string[]) dat bij opstart wordt aangemaakt. Elke cel bevat op dat moment "". Eens dat gebeurd is, tonen we het huidige rooster en wachten we tot de gebruiker een cel aanpast, tot het einde ter tijden, of tot het programma gestopt wordt.

In Flowgorithm ziet dit er zo uit:

Ik vul eerst de lege string in in elke cell, omdat er anders de defaultwaarde null staat. Lege cellen gedragen zich echter als cellen waarbij we letter per letter een backspace hebben uitgevoerd, dus als een cel met een lege string. Bovendien zullen we null verderop voor iets anders gebruiken.

Voorlopig kan je BerekenEnToonRooster en WijzigCel met een lege body implementeren. Zo kan je tenminste het programma compileren.

Aan jullie om dit om te zetten naar code in C#. Schrijf de code met de hand. Deze code begint trouwens in Main, terwijl het de bedoeling is dat je je spreadsheet kan opstarten van uit je keuzemenu, dus noem je methode TextCellMain en zet ze in de klasse TextCell.

Weergave van het rooster

Eigenlijk moeten we dus de twee methodes implementeren die in de lus worden uitgevoerd. We zullen beginnen met de methode BerekenEnToonRooster, omdat we dan zo snel mogelijk onze spreadsheet kunnen zien. We zullen eerst alleen het "toon"-aspect afhandelen. Met andere woorden, we negeren even berekeningen en doen alsof elke cel gewoon een tekstwaarde bevat.

We willen wel al dat onze cellen naast elkaar getoond worden en dat boven elke cel een identifier staat.

Om de identifiers te tonen en om ze te kunnen vertalen naar getallen, moet je wat wiskunde gebruiken. Dat is hier niet de focus, dus je krijgt hier twee methoden voor in de cursustekst. Deze heten GetalVoorstellingNaarLetters en LetterVoorstellingNaarGetal. Je moet ze kopiëren uit de tekst en in je klasse zetten. De eerste zet een getal om naar een lettercombinatie (die je boven de cellen kan tonen), de tweede werkt in de omgekeerde richting:

public static string GetalVoorstellingNaarLetters(int getal) {
    string alfabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    int aantalSymbolen = alfabet.Length;
    string resultaat = "";
    int resterend = getal;
    bool laatsteKeer = false;
    while (!laatsteKeer) {
        if (resterend <= aantalSymbolen) {
            laatsteKeer = true;
        }
        int karakterIndex = (resterend - 1) % aantalSymbolen;
        resultaat = alfabet[karakterIndex] + resultaat;
        resterend = (resterend - 1) / aantalSymbolen;
    }
    return resultaat;
}

public static int LetterVoorstellingNaarGetal(string letters) {
    string alfabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    int aantalSymbolen = alfabet.Length;
    int resultaat = 0;
    for (int i = letters.Length - 1; i >= 0; i--) {
        int exponent = letters.Length - i - 1;
        resultaat += (alfabet.IndexOf(letters[i]) + 1) * (int) Math.Pow(aantalSymbolen, exponent);
    }
    return resultaat;
}

Voor de weergave van de cellen starten we hiermee:

for(int i = 0; i < rooster.Length; i++) {
  Console.Write("|");
  string tekstVoorstelling = GetalVoorstellingNaarLetters(i+1);
  Console.Write(tekstVoorstelling);
}
Console.WriteLine("|");
for(int i = 0; i < rooster.Length; i++) {
  Console.Write("|");
  Console.Write(rooster[i]);
}
Console.WriteLine("|");

Een probleem hier is dat niet elke cel even breed is. Probeer maar eens uit.

Als oplossing zullen we elke cel 10 karakters breed tonen. Inhoud die verder gaat, wordt niet getoond.

for(int i = 0; i < rooster.Length; i++) {
    Console.Write("|");
    string tekstVoorstelling = GetalVoorstellingNaarLetters(i+1);
    Console.Write(tekstVoorstelling.PadRight(10).Substring(0,10));
}
Console.WriteLine("|");
for(int i = 0; i < rooster.Length; i++) {
    Console.Write("|");
    Console.Write(rooster[i].PadRight(10).Substring(0,10));
}
Console.WriteLine("|");

Met een beetje creativiteit kan je elke cel exact zo breed maken als nodig is voor de content, maar om het project kort te houden, doen we dat voorlopig niet.

Invullen van waarden

Dit is vrij eenvoudig. We moeten gewoon vragen welke cel we willen invullen, wat we er in willen plaatsen en dan die positie van de array invullen. Er is wel één puntje waar we aandacht aan moeten besteden.

We hebben tijdens de les rond methoden gezien dat methoden werken met een kopie van het meegegeven argument. Het gevolg daarvan was dat je een parameter kon wijzigen in een methode en dat je dit niet meer zag na de oproep van de methode. Ik herhaal even het voorbeeld uit de contactles:

public static void VeranderGetal(int getal) {
  getal = getal - 1;
  Console.WriteLine($"Het getal is {getal}");
}

public static void Main() {
  int getal = 4;
  Console.WriteLine($"Het getal is {getal}");
  VeranderGetal(getal);
  Console.WriteLine($"Het getal is {getal}");
}

De reden dat op het einde Het getal is 4 verschijnt, is dat de methode VeranderGetal een kopie van de waarde heeft gekregen om mee te werken.

Om technische redenen, die we in het vervolgvak uitgebreider zullen bespreken, lijkt dit op het eerste zicht niet helemaal te kloppen voor arrays. Je kan namelijk een array als argument van een methode meegeven. Als je dan een aanpassing doet op een indexpositie van die array, blijft die zichtbaar na de methode. Voor dit vak is dit de enige uitzondering op de regel dat methodes argumenten niet kunnen wijzigen.

Het slechte nieuws is dus dat sommige soorten data complexere regels volgen. Het goede nieuws is dat het heel makkelijk wordt om de methode WijzigCel te schrijven.

public static void WijzigCel(string[] rooster) {
  Console.WriteLine("Welke cel wil je wijzigen?");
  int celIndex = LetterVoorstellingNaarGetal(Console.ReadLine()) - 1;
  Console.WriteLine("Wat wil je hier invullen?");
  rooster[celIndex] = Console.ReadLine();
}

Nu kunnen we waarden invullen en zien we ze verschijnen. Probeer maar eens uit.

Berekeningen toevoegen

Een spreadsheet is geen spreadsheet als je geen berekeningen hebt. We moeten dit slim aanpakken. Cellen kunnen verwijzen naar andere cellen, of die nu eerder of later komen. Bovendien moeten de berekeningen zelf blijven staan in de spreadsheet. We mogen ze dus niet meteen uitrekenen op het moment dat ze worden ingetypt, want dan zouden ze hun resultaten niet updaten.

We zullen volgende aanpak volgen voor een spreadsheet met berekeningen:

We maken dus eerst een kopie van onze spreadsheet, maar we vullen alleen de cellen zonder berekening in. Voor de cellen met een berekening vullen we niets in, zodat er null staat, de defaultwaarde van een variabele van het type string. Herinner je dat dit niet hetzelfde is als "". Vanaf dan gaan we op zoek naar cellen waarvoor we nog geen resultaat hebben. Als alle nodige inputs voor die cellen al gekend zijn, vullen we ze in. We herhalen dit tot we voor elke cel een resultaat hebben.

De eerste kopie maken doen we als volgt:

string[] berekendRooster = new string[rooster.Length];
KopieerCellenZonderFormule(rooster,berekendRooster);

De hulpmethode schrijven we als volgt:

public static void KopieerCellenZonderFormule(string[] roosterIn, string[] roosterUit) {
    for (int cel = 0; cel < roosterIn.Length; cel++) {
        if(!roosterIn[cel].StartsWith("=")) {
            roosterUit[cel] = roosterIn[cel];
        }
    }
}

Dit maakt dus een tweede array die er uitziet zoals de eerste, maar zonder de formules. Merk ook op dat de hoofdingen in de tekening wel bestaan, maar in code zijn ze niet aanwezig. Ze stellen gewoon indexposities van een array voor, waarbij A wijst op de eerste cel, B op de tweede,...

In een volgende stap doorlopen we de berekende array en proberen we de "vraagtekens" in te vullen. Hoe vaak dit moet gebeuren, hangt af van hoe veel formules er staan en wat de verbanden tussen cellen zijn. Zolang onze spreadsheet juist is opgesteld, eindigt het proces uiteindelijk wel en wordt alles ingevuld. We vatten dit als volgt samen:

while (BevatNullWaarden(berekendRooster)) {
  BerekenOntbrekendeCellenEenKeer(rooster, berekendRooster);
}

Voor de hulpmethode BerekenOntbrekendeWaardenEenKeer, die één stapje op de tekening voorstelt, doorlopen we alle reeds berekende cellen. Telkens we een nog niet berekende cel tegenkomen, zoeken we de formule op en proberen we die uit te rekenen voor enkel die cel. De implementatie ziet er zo uit:

public static void BerekenOntbrekendeWaardenEenKeer(string[] rooster, string[] berekendRooster) {
    for(int cel = 0; cel < rooster.Length; cel++) {
        if (berekendRooster[cel] is null) {
            berekendRooster[cel] = BerekenOntbrekendeCelRechtstreeks(rooster[cel].Substring(1), berekendRooster);
        }
    }
}

Om één formule (met andere woorden, een som) uit te rekenen, moeten we eerst bepalen welke onderdelen we moeten optellen. Dat kunnen getallen of andere cellen zijn. We kunnen de tekstvoorstelling van deze onderdelen verkrijgen via de Split-methode van een string. Deze breekt een string in stukjes op basis van het meegegeven argument.

Laat ons dit al eens uitproberen met sommen van uitsluitend getallen:

public static string BerekenOntbrekendeCelRechtstreeks(string formule, string[] berekendRooster) {
    string[] somOnderdelen = formule.Split('+');
    int som = 0;
    for (int i = 0; i < somOnderdelen.Length; i++) {
        som += Convert.ToInt32(somOnderdelen[i]);
    }
    return som.ToString();
}

Dit werkt als je formules alleen getallen optellen. Nu moeten we nog zorgen dat verwijzingen naar andere cellen herkend worden.

Om dit klaar te spelen, moeten we het verschil kunnen zien tussen iets als A en 1. We houden het simpel: we kijken gewoon of het stuk tekst begint met een cijfer. Het kan nauwkeuriger, maar omdat formules alleen getallen of cellen kunnen bevatten, is het voor ons voldoende.:

public static bool IsGetal(string voorstelling) {
            return voorstelling.StartsWith("0") ||
                   voorstelling.StartsWith("1") ||
                   voorstelling.StartsWith("2") ||
                   voorstelling.StartsWith("3") ||
                   voorstelling.StartsWith("4") ||
                   voorstelling.StartsWith("5") ||
                   voorstelling.StartsWith("6") ||
                   voorstelling.StartsWith("7") ||
                   voorstelling.StartsWith("8") ||
                   voorstelling.StartsWith("9");
}

Nu kunnen we een methode schrijven die namen van cellen vervangt door de reeds uitgerekende waarde, maar getallen met rust laat:

public static string WaardeVanCel(string waarde, string[] rooster) {
  if (IsGetal(waarde)) {
    return waarde;
  }
  else {
    return rooster[LetterVoorstellingNaarGetal(waarde) - 1];
  }
}

Nu kunnen we zorgen dat sommen ook identifiers van cellen mogen bevatten. We gebruiken de methode die we net geschreven hebben om de waarde van de cel op te zoeken. Als de waarde van de cel nog niet gekend is, kunnen we ook geen waarde voor een formule op basis ervan berekenen. We hebben de basisonderdelen van de som niet echt meer nodig eens we ze hebben opgezocht, dus we mogen ze overschrijven:

public static string BerekenOntbrekendeCelRechtstreeks(string formule, string[] berekendRooster) {
  string[] somOnderdelen = formule.Split('+');
  for (int i = 0; i < somOnderdelen.Length; i++) {
    somOnderdelen[i] = WaardeVanCel(somOnderdelen[i], berekendRooster);
    if(somOnderdelen[i] is null) {
      return null;
    }
  }
  int som = 0;
  for (int somOperand = 0; somOperand < somOnderdelen.Length; somOperand++) {
    som += Convert.ToInt32(somOnderdelen[somOperand]);
  }
  return som.ToString();
}

Nu nog even uittesten.

Hoe veel cellen telt je spreadsheet?
8
|A         |B         |C         |D         |E         |F         |G         |H         |
|          |          |          |          |          |          |          |          |
Welke cel wil je wijzigen?
D
Wat wil je hier invullen?
4
|A         |B         |C         |D         |E         |F         |G         |H         |
|          |          |          |4         |          |          |          |          |
Welke cel wil je wijzigen?
F
Wat wil je hier invullen?
=D+D
|A         |B         |C         |D         |E         |F         |G         |H         |
|          |          |          |4         |          |8         |          |          |
Welke cel wil je wijzigen?
A
Wat wil je hier invullen?
=F+3
|A         |B         |C         |D         |E         |F         |G         |H         |
|11        |          |          |4         |          |8         |          |          |
Welke cel wil je wijzigen?
H
Wat wil je hier invullen?
9
|A         |B         |C         |D         |E         |F         |G         |H         |
|11        |          |          |4         |          |8         |          |9         |
Welke cel wil je wijzigen?
C
Wat wil je hier invullen?
=A+H
|A         |B         |C         |D         |E         |F         |G         |H         |
|11        |          |20        |4         |          |8         |          |9         |

Werken met arrays van strings

Split

De Split methode laat toe een string te splitsen op een bepaald teken. Het resultaat is steeds een array van strings.

Uiteraard kan je dit dus gebruiken om op eender welk char te splitsen en dus niet enkel op een ',' (komma).

Dit is bijzonder nuttig voor het verwerken van bestanden met gestructureerde data, bijvoorbeeld CSV-bestanden. Dit zijn bestanden waarin elke regel een groepje verwante gegevens voorstelt, gescheiden via een afgesproken symbool zoals ,. Deze files worden vaak gebruikt als exportformaat voor spreadsheets en databasetabellen.

Join

Via Join kunnen we array van string terug samenvoegen. Het resultaat is een nieuwe string.

Volgende voorbeeld zal de eerder array van het vorige voorbeeld opnieuw samenvoegen maar nu met telkens een ; tussen iedere string:

Input en output van tekstbestanden

Text files lezen algemeen

De System.IO namespace bevat tal van nuttige methoden en klassen om met bestanden te werken. Om gebruik hiervan te maken plaats je bovenaan je file:

Via System.File.ReadAllLines() kunnen we een tekstbestand uitlezen. De methode geeft een array van string terug. Per lijn die eindigt met een newline (onder Windows \r) zal een nieuwe string aan de array toegevoegd worden.

Deze methode probeert automatisch de juiste encodering van het bestand te detecteren. Dit zal niet voor elke encodering werken. Als je vreemde karakters ziet, zal je moeten uitzoeken wat de encodering is en uitdrukkelijk aangeven dat je deze wenst te gebruiken.

CSV uitlezen

Dankzij ReadAllLines en Split hebben we nu alle bouwstenen om eenvoudig een csv-bestand te verwerken.

Stel je voor dat een bestand soccer.csv volgende tekst bevat:

Volgende code zal dit bestand uitlezen en de individuele data op het scherm tonen:

Text files schrijven

Je kan tekst uit een bestand lezen, maar uiteraard kan je ook naar een bestand wegschrijven. De 2 eenvoudigste manieren zijn:

  • File.WriteAllText: deze gebruik je als je 1 enkele string wilt wegschrijven. Typisch bevat deze tekst newline karakters.

  • File.WriteAllLines: deze is de omgekeerde van ReadAllLines() en zal een array van strings wegschrijven. Elke string in de array wordt geschreven als een regel, dus het is alsof je de strings eerst verbindt met een newline via String.Join en dan wegschrijft met File.WriteAllText.

Een voorbeeld:

Opgelet met het schrijven naar bestanden: dit zal onherroepelijk het target bestand overschrijven. .Gebruik if(File.Exists(pathtofile)) om te controleren of een bestand bestaat of niet. Eventueel kan je dan aan de gebruiker bevestiging vragen of je deze effectief wilt overschrijven.

Interpolatie met formattering

Via stringinterpolatie schrijf je je string min of meer zoals hij er uiteindelijk moet uitzien, maar vervang je de niet-letterlijke delen door geformatteerde waarden. Dit levert een goed leesbaar resultaat.

Door het $-teken VOOR de string te plaatsen geef je aan dat alle delen in de string die tussen accolades staan als code mogen beschouwd worden. Een voorbeeld maakt dit duidelijk:

In dit geval zal dus de inhoud van de variabele name tussen de string op de plek waar nu {name} staat geplaatst worden. Idem voor age. Dit mag, zelfs al is age geen string: hetgeen tussen de accolades staat, wordt altijd intern omgezet naar een string voor het in het resultaat wordt geplaatst. Dit gebeurt via de methode ToString, die voor elk soort data bestaat. Dus eigenlijk wordt het achter de schermen:

Berekeningen doen bij string interpolatie

Je mag eender welke expressie tussen de accolades zetten bij string interpolation. Een expressie is iets dat je kan uitrekenen en dat je een resultaat oplevert. Bijvoorbeeld:

Alle expressies tussen de accolades zullen eerst uitgevoerd worden voor ze tussen de string worden geplaatst. De uitvoer wordt nu dus: Ik ben Finkelstein en ik ben 17 jaar oud.

Eender welke expressie is toegelaten, dus je kan ook complexe berekeningen of zelfs andere methoden aanroepen:

Mooier formatteren

Bij stringinterpolatie kan je ook bepalen hoe de te tonen variabelen en expressies juist weergegeven moeten worden. Je geeft dit aan door na de expressie, binnen de accolades, een dubbelpunt te plaatsen gevolgd door de manier waarop moet geformatteerd worden:

Wil je bijvoorbeeld een kommagetal tonen met maar 2 cijfers na de komma dan schrijf je:

Hier stelt 2 het aantal cijfers na de komma voor. Je kan zelf kiezen om meer of minder cijfers te tonen door het getal aan te passen. Dit voorbeeld is gelijkaardig aan Console.WriteLine(Math.Round(number,2)), maar niet identiek. Dat komt omdat de geformatteerde string verplicht twee cijfers na de komma toont, zelfs als die niets veranderen aan de waarde van het getal. Als je een double zonder formattering weergeeft, worden nullen achteraan niet getoond. Dus voor getallen als 12 of 15.1 of 4.999(met minder dan twee cijfers na de komma na afronding) maakt het een verschil.

Nog enkele nuttige vormen:

  • D5: geheel getal bestaande uit 5 cijfers (123 wordt 00123) (werkt enkel op gehele getallen en je kan het getal 5 vervangen door een positieve waarde naar keuze)

  • C: geldbedrag (12,34 wordt € 12,34: teken van valuta afhankelijk van instellingen pc)

Je kan je data ook een vaste breedte en uitlijning geven. Dit doe je door meteen na de waarde in kwestie een komma te plaatsen, met daarna de gewenste breedte. De tekst wordt dan rechts gealigneerd. Je kan ook een minteken gebruiken en dan wordt de tekst links gealigneerd. Bijvoorbeeld:

Dit levert:

Dus de tekst "hallo" wordt opgevuld totdat hij in totaal 20 karakters telt. Elke regel telt 27 karakters, want " wereld" is niet mee geformatteerd, maar is gewoon achter de geformatteerde tekst gezet.

Manueel formatteren

Formattering is een handig hulpmiddel, maar het zal niet altijd alle scenario's dekken. In het voorbeeld hierboven is het bijvoorbeeld verplicht een constante waarde te gebruiken. Je kan geen variabele invullen waar 20 staat. Het is bijvoorbeeld ook niet mogelijk tekst te centeren (in plaats van links of rechts uit te lijnen). Dat hoeft geen probleem te zijn: je kan altijd een methode schrijven die tekst op de gewenste manier omzet.

Ingebouwde methodes voor formattering

Via PadLeft en PadRight kan je tekst opvullen tot de gewenste lengte met andere karakters dan een spatie. Bijvoorbeeld, voor hetzelfde effect als eerder:

Zoals vaak lever je hiermee wat gemak in voor extra flexibiliteit. Je kan namelijk opvullen met een karakter naar keuze, tot een variabele lengte:

Eigen methodes voor formattering

Als er geen mehode is om een string juist te tonen, kan je er altijd zelf een maken. Je zorgt dat de string en extra opties zoals de gewenste breedte parameters zijn en je geeft de geformatteerde string terug als resultaat. Er is bijvoorbeeld geen methode om tekst in te korten tot een maximale lengte, maar je kan ze zelf schrijven als volgt:

Je zou bijvoorbeeld ook een methode kunnen schrijven om tekst centraal te aligneren.

Voorstelling van tekst

Wat is Unicode?

Alle gegevens in het geheugen van je computer worden voorgesteld als een opeenvolging van binaire cijfers. Dat is gewoon veruit de handigste manier om computers te bouwen. Helaas is het niet de handigste voorstelling. Als je een bestand opent in een text editor, zie je bijvoorbeeld niet 0010001110001101010101110 (enzovoort), maar wel hallo wereld, مرحبا بالعالم of 你好,世界. Dat komt omdat er afspraken gemaakt zijn rond welke reeksen bits welke karakters voorstellen. Die afspraken zijn doorheen de tijd ontstaan. Een van de simpelste is 7-bit ASCII, waarbij één byte gebruikt werd om een karakter voor te stellen. 7 bits bepaalden welk karakter werd voorgesteld (er waren er maximum 128), de achtste diende om te controleren dat er geen datacorruptie was opgetreden.

Naarmate computers in meer regio's gebruikt werden, werd duidelijk dat 128 karakters niet volstonden om elke taal van de wereld digitaal voor te stellen. Laat staan de emoji die we vandaag hebben. Als oplossing voor dat probleem is de Unicode standaard vastgelegd. Deze koppelt, eenvoudig gesteld, aan een hele grote reeks getallen een overeenkomstig symbool.

Wat is een karakterset?

De Unicode standaard is niet genoeg om tekst voor te stellen in de praktijk. Je moet immers ook weten hoe je bits moet samen nemen. Anders gezegd: maak je groepjes van één byte, van twee bytes, van drie bytes,... om zo de getallen te vinden die symbolen voorstellen? Er zijn meerdere antwoorden op die vraag en in de praktijk gaat het eerder om combinaties. Zo kan je zeggen dat je mééstal 1 byte gebruikt, maar soms meer als je aan één byte niet genoeg hebt (omdat je een symboolnummer groter dan 255 hebt). Hoe dat precies werkt, laten we hier achterwege. Maar we onthouden wel dat we hier per bestand een afspraak rond moeten maken. Deze afspraak noemen we de karakterset of encodering van ons bestand. C#-programma's veronderstellen intern bijvoorbeeld (net als JavaScript programma's) dat je gewoonlijk twee bytes gebruikt om een karakter voor te stellen, maar ze hebben een achterpoortje waardoor je meer dan twee bytes kan gebruiken. Deze encodering heet UTF-16. Dit betekent niet dat je in C# niet met bestanden in een andere karakterset kan werken of zelfs dat je .cs-files UTF-16 moeten gebruiken. Maar het bepaalt wel hoe we speciale karakters kunnen noteren via escape symbolen.

Hoe stellen we een individueel karakter voor?

Om één karakter voor te stellen, gebruiken we het type char. Je kan hier een waarde aan toekennen door ze tussen enkele quotes te plaatsen, als volgt:

Tegelijkertijd kan je er mee rekenen. Dan ga je eigenlijk met het Unicode nummer van dat symbool werken. Om terug toe te kennen aan een karakter, moet je wel weer casten. Bijvoorbeeld:

Dit werkt, want in de Unicode standaard komt de g na de f.

Je kan karakters ook noteren via hun Unicode nummer, door een "Unicode escape" te gebruiken. Hierbij noteer je eerst een backslash, dan een u en dan het Unicode nummer in hexadecimale notatie. Als je dit print op je scherm, zie je net hetzelfde als met de notatie hierboven. Het voordeel is dat je het symbool niet op je toetsenbord moet hebben staan.

Er zijn ook "gewone" escapes voor tekens die je vaak nodig hebt. Deze zijn onder andere '\n' (newline) en '\t' (tab). '\u000A' en '\u0009' werken ook, maar zijn wat lastiger te onthouden. Let wel op: als je een backslash wil gebruiken zonder dat deze een escape sequentie activeert, moet je de backslash zelf escapen. Dus \\ stelt eigenlijk het karakter \ voor.

Wat hier verder opvalt: we hebben maximum 4 hexadecimale cijfers, dus in theorie 65536 karakters. Het is waar dat we maar 65536 waarden van het type char hebben. Maar binnenin een string kan je ook karakters noteren en in deze situatie zijn er combinaties van karakters die als één geheel gezien worden. We noemen deze surrogate pairs en ze geven weer als één karakter. Bijvoorbeeld:

Je kan emoji gebruiken in je programmatekst, maar je moet de terminal ook verwittigen dat je dit wil doen. Hiervoor zet je de encodering van de terminal via Console.OutputEncoding = Encoding.UTF8.

Verbatim strings

Door een @ (verbatim character) voor een string te plaatsen zeggen we concreet: "de hele string die nu volgt moet je beschouwen zoals hij er staat. Je mag alle escape karakters negeren en er mogen line breaks voorkomen in deze string."

Dit wordt vaak gebruikt om een filepath iets leesbaarder te maken.

  • Zonder verbatim: string path= "c:\\Temp\\myfile.txt";

  • Met verbatim: string path= @"c:\Temp\myfile.txt";

Bovenstaande strings zijn identiek voor C#! De @ geeft geen enkele extra functionaliteit, maar zorgt gewoon dat we dezelfde tekst (meerbepaald: backslashes en line breaks) wat makkelijker kunnen intypen.

Volgende stukken code doen dus hetzelfde:

Intern is er géén verschil. Voor de WriteLine wordt uitgevoerd, worden beide waarden in dezelfde voorstelling gegoten. Maar in dit geval is de eerste versie wel veel makkelijker te lezen voor de programmeur.

Oefeningen

Inleiding

Deze oefeningen maak je als statische methoden van een klasse NumeriekeData. Je kan elk van deze methoden uitvoeren via een keuzemenu, zoals bij vorige hoofdstukken.

H8-LengteOppervlakteVolume

Leerdoelen

  • Werken met machten

Functionele analyse

Schrijf een programma om, gegeven de lengte van een zijde, de oppervlakte van een vierkant met die zijde en het volume van een kubus met die zijde te berekenen. Je mag maximum vier keer gebruik maken van de variabele met daarin de lengte van de zijde, inclusief de initialisatie.

Technische analyse

De oppervlakte bepaal je door de zijde met zichzelf te vermenigvuldigen. Bijvoorbeeld, een zijde van 10m betekent een oppervlakte van 10m * 10m = 100m². Het volume bepaal je door nog een keer met de zijde te vermenigvuldigen. Een zijde van 10m betekent dus een oppervlakte van 1000m³. Maak gebruik van een methode uit Math.

Noem je methode LengteOppervlakteVolume.

Voorbeeldinteractie

Oefening: H8-schaar-steen-papier

Leerdoelen

  • een taak herhaaldelijk uitvoeren met een lus

Functionele analyse

Maak een applicatie waarbij de gebruiker steen-schaar-papier met de computer kan spelen. De gebruiker kiest telkens steen, schaar of papier en drukt op enter. Vervolgens kiest de computer willekeurig steen, schaar of papier.

Vervolgens krijgt de winnaar 1 punt:

  • Steen wint van schaar, verliest van papier

  • Papier wint van steen, verliest van schaar

  • Schaar wint van papier, verliest van steen

  • Indien beide hetzelfde hebben wint niemand een punt.

De eerste (pc of gebruiker) die 10 punten haalt wint.

Noem je methode SchaarSteenPapier.

Technische analyse

  • Genereer een willekeurig getal tussen 1 en 3 om de computer te laten kiezen.

  • Teken een flowchart!

Voorbeeldinteractie

(Helemaal op het einde)

of

Oefening: TextCell met willekeurige getallen

Maak voor deze oefening een aparte klasse TextCellMetRandom. Kopieer hierin de code van je klasse TextCell en pas daarna pas aan.

Functionele analyse

We willen onze spreadsheets gebruiken voor simulaties. Hiervoor hebben we willekeurige data nodig.

Technische analyse

Zorg ervoor dat een cel in TextCell ook een formule van de vorm =rand() kan bevatten. Dit initialiseert de cel op een willekeurige waarde tussen 1 en 10.

Oefening: TextCell met kommagetallen

Maak voor deze oefening een aparte klasse TextCellMetKomma. Kopieer hierin de code van je klasse TextCell en pas daarna pas aan.

Functionele analyse

We willen complexere berekeningen doen in TextCell. Zorg ervoor dat kommagetallen ondersteund worden, maar wel steeds getoond worden met maximum twee cijfers na de komma.

Technische analyse

Pas op de juiste plaatsen alle code die een int veronderstelt aan naar een double. Zorg ervoor dat, na het uitrekenen van alle formules, alle cellen klassiek afgerond worden tot 2 cijfers na de komma.

N-dimensionale arrays

Organisatievormen van data

Voorlopig hebben we enkel met 1-dimensionale arrays gewerkt. Je kan er echter ook meerdimensionale maken. In plaats van "reeksen" van data kan je deze zien als tabellen of als figuren.

Als we een array tekenen, doen we dat vaak als een lijn met vakjes op. En als we door een volledige array lopen, doen we dat door de index steeds te verhogen. Om deze reden zeggen we dat arrays een "lineaire" gegevensstructuur zijn, dus "lijnvormig". Een andere manier om dit uit te drukken is "ééndimensionaal".

Een array met de getallen 4, 9, 2, 7, 3, 6, 1, 12 en 9 zouden we ons kunnen inbeelden als volgt:

Het maakt niet echt uit of je dit ziet van links naar rechts, van boven naar onder,... Wat wel belangrijk is, is dat je dit ziet als een reeks waar je in een richting door loopt. Onderstaande voorstelling kan dus ook:

Vaak is het handig om niet met een ééndimensionale voorstelling te werken, maar met een tweedimensionale of driedimensionale voorstelling. Dit kan bijvoorbeeld zijn omdat we data hebben zoals een Excel sheet, een rooster voor het spelletje OXO of vier-op-een-rij, een Sudokupuzzel, een afbeelding (die bestaat uit pixels),...

Een voorbeeld. Kan jij van onderstaande afbeelding in één oogopslag zeggen of er OXO op staat, als de eerste waarde staat voor het vakje linksboven en de laatste voor het vakje rechtsonder?

Niet meteen. Je moet alles eerst "vertalen" in je hoofd naar dit:

Nu kan je veel makkelijker aflezen dat er in de eerste kolom inderdaad OXO staat. Dus de organisatie van je data kan het makkelijker of moeilijker maken bepaalde taken uit te voeren, omdat we visueler kunnen werken.

Meerdimensionale arrays leveren hetzelfde voordeel op wanneer je programmeert. Ze staan toe data op te delen in roosters, zodat het makkelijker wordt cellen in een bepaalde richting te doorlopen (rij per rij, kolom per kolom, in de diepte,...). Dit is vaak nuttig om zaken die we "op het zicht" doen te kunnen toepassen op arrays.

Met meerdimensionale arrays kan je technisch niet meer of minder dan met ééndimensionale arrays. Maar afhankelijk van het probleem maken ze het programmeerwerk wel een pak eenvoudiger.

Syntax

De syntax voor een meerdimensionale array is heel gelijkaardig aan die voor een eendimensionale array, maar per dimensie boven de eerste zet je een komma tussen de rechte haken. Bijvoorbeeld:

Hierin passen dus 9 strings, maar de extra structuur zal het makkelijker maken rij per rij of kolom per kolom te werken.

Je kan de array ook als volgt initialiseren.

Of hij mag al ingevuld zijn:

Technisch gesproken bevatten de binnenste accolades groepjes waarden in de tweede dimensie van de array en de buitenste ordenen deze groepjes dan in de eerste dimensie. Maar het volstaat als je onthoudt dat je hier zaken kan noteren alsof ze in rijen en kolommen stonden.

Je kan nu elementen opvragen door per dimensie een index mee te geven. Indexen blijven nog steeds vanaf 0 tellen. Bijvoorbeeld:

Lengte van iedere dimensie in een n-dimensionale matrix

Indien je de lengte opvraagt van een meer-dimensionale array dan krijg je het totaal aantal posities in de array. Onze OXO-array zal bijvoorbeeld dus lengte 9 hebben. Je kan echter de lengte van iedere aparte dimensie te weten komen met de GetLength() methode die iedere array heeft. Als parameter geef je de dimensie mee waarvan je de lengte wenst.

Het aantal dimensies van een array wordt trouwens weergegeven door de Rank eigenschap die ook iedere array heeft. Bijvoorbeeld:

Geneste iteratie

Itereren over een tweedimensionale array

Met meerdimensionale arrays kunnen we de richtingen los van elkaar bekijken. Bijvoorbeeld, om ons OXO-rooster af te printen (eerste rij op één regel, tweede rij op een volgende regel, derde rij op een derde regel), kunnen we dit doen:

Als we het rooster in één dimensie zouden voorstellen met een variabele oxoArray, moesten we dit doen:

We hebben de tweedimensionale versie voor een beter inzicht met een hulpmethode gedaan. Dat hoeft niet, want je kan de inhoud van PrintRij ook rechtstreeks invullen. Dan krijg je een geneste lus:

In het begin kan deze structuur wat intimiderend zijn, maar er is niets nieuws aan deze luscode. Kijk eerst naar wat één uitvoering doet en zie dat als één geheel. Denk dan na over wat er gebeurt als je dat geheel herhaalt, zonder na te denken over hoe dat geheel werkt.

Itereren met onderling afhankelijke indexen

In het vorige voorbeeld toonden we per rij even veel symbolen. Dus de werking van PrintRij hing niet echt af van de kolom waarop we ons bevonden. Dat is niet altijd zo. Soms is er interactie tussen de tellers van de buitenste en de binnenste for-lus.

Volgend voorbeeld toont hoe je enkel een driehoekig deel van een rooster toont:

Opnieuw kan je de lussen in elkaar schuiven:

En opnieuw hanteer je dezelfde denkwijze om hier uit wijs te raken. Kijk eerst naar wat in de lus staat. Zie rij gewoon als een getal dat bepaalt hoe veel karakters je maximum kan printen, zonder na te denken over hoe dat getal verandert. Pas als je de werking van de inhoud van de lus goed in je hoofd hebt, denk je na over wat er gebeurt als je die inhoud gaat herhalen met verschillende waarden voor rij.

Itereren zonder arrays

Geneste lussen komen vaak voor bij meerdimensionale arrays, maar dat is vooral omdat de twee technieken goed samen gaan. Er is geen verplichting om ze samen te gebruiken.

Je kan geneste lussen gebruiken voor allerlei taken waarbij je herhaling hebt op meerdere niveaus. Bijvoorbeeld voor volgende figuur:

Hier zijn twee vormen van herhaling:

  • de herhaling die het snelst terugkeert, namelijk van links naar rechts (we printen een aantal keer hetzelfde getal)

  • de herhaling die minder vaak gebeurt, namelijk van boven naar onder (we printen een aantal keer een regel)

We denken eerst na over de herhaling die het snelst terugkeert. We kennen alles om een getal een aantal keer (gelijk aan zichzelf) te printen:

We kennen ook alles om een newline een aantal keer te printen:

Om de twee lussen te combineren, moeten we vermijden dat we dezelfde naam i gebruiken, dus we hernoemen die van de binnenste herhaling naar j. En het aantal keer dat we een getal op een regel willen herhalen, is gelijk aan het nummer van die regel. Dan krijgen we dus:

Het stappenplan om om het even welke fruittaart te maken.
De code om snel twee fruittaarten te maken op basis van hetzelfde stappenplan.

(meer kennisclips lager op de pagina)

Je kan .

We kunnen een nog niet gekende cel invullen door de onderdelen van de formule op te zoeken, op voorwaarde dat we ze al kennen. De blauwe pijlen tonen van waar de nodige informatie komt. De oranje pijlen tonen waar we informatie missen.

Alle format specifiers staan . Voor deze cursus volstaat het dat je deze specifiers kent voor de verschillende soorten data.

Het is duidelijker als je dit in de praktijk ziet. Volgende voorbeelden komen van :

Je hoeft de werking van surrogate pairs voor deze cursus niet in detail te kennen, maar als je ooit een applicatie schrijft die emoji,... bevat, vind je de details.

Kennisclip basis
Kennisclip narrowing en widening
Kennisclip Convert klasse
alle conversie-mogelijkheden hier bekijken
Kennisclip return
Kennisclip return en parameters samen
Kennisclip
string data= "12,13,20";
string[] gesplitst= data.Split(',');

for(int i=0; i<gesplitst.Length; i++)
{
    Console.WriteLine(gesplitst[i]);
}
string joined = String.Join(";", gesplitst);
using System.IO;
// wat dus ook kan: File.ReadAllLines("C:\\mypoem.txt")
string[] lines = File.ReadAllLines(@"C:\mypoem.txt");
for (int i = 0; i < lines.Length; i++)
{
    Console.WriteLine(lines[i]);
    Console.WriteLine("----------");
}
Dams;Tim;1981
Hamdaoui;Mounir;1984
Stoffels;José;1950
string[] lijnen = File.ReadAllLines(@"C:\soccerstars.csv");
for (int i = 0; i < lijnen.Length; i++)
{
    string[] kolomwaarden = lijnen[i].Split(';');
    Console.WriteLine($"Voornaam speler {i}= {kolomwaarden[1]}" );
    Console.WriteLine($"Achternaam speler {i}= {kolomwaarden[0]}");
    Console.WriteLine($"Geboortejaar speler {i}= {kolomwaarden[2]}");
}
string[] stringArray = new string[]
    {
        "cat",
        "dog",
        "arrow"
    };


File.WriteAllLines("C:\\file.txt", stringArray);
string name = "Finkelstein";
int age = 17;
string result = $"Ik ben {name} en ik ben {age} jaar oud.";
string result = $"Ik ben {name.ToString()} en ik ben {age.ToString()} jaar oud.";
string result = $"Ik ben {name} en ik ben {age+4} jaar oud.";
string result = $"Ik ben {age*age+(3%2)} jaar oud.";
double number = 12.345;
Console.WriteLine($"{number:F2}");
Console.WriteLine($"{"hallo",20} wereld");
Console.WriteLine($"{"hallo",-20} wereld");
               hallo wereld
hallo                wereld
Console.WriteLine($"{"hallo".PadLeft(20)} wereld");
Console.WriteLine($"{"hallo".PadRight(20)} wereld");
Console.WriteLine("Hoe breed wil je de string?");
int breedte = Convert.ToInt32(Console.ReadLine());
Console.WriteLine($"{"hallo".PadLeft(breedte,'!')} wereld");
Console.WriteLine($"{"hallo".PadRight(breedte,'!')} wereld");
static string Trunkeer(string input, int lengte) {
    return input.Substring(0, Math.Min(input.Length, lengte));
}
char letterF = 'f';
char recycle = '♻';
char letterG = (char) ('f' + 1);
char recycle = '\u267B';
Console.WriteLine("Dit is één symbool: \ud835\udcb3");
string tekst = @"Back

\

Slash";
Console.WriteLine(tekst);
string tekst = "Back\n\n\\\n\nSlash";
Console.WriteLine(tekst);
Hoe lang is de zijde in meter?
> 10
De lengte is: 10m
De oppervlakte is: 100m²
Het volume is: 1000m³
Maak een keuze:
1 voor schaar
2 voor steen
3 voor papier
> 2
De computer kiest steen!
Niemand wint deze ronde!
Jij hebt 0 punten, de computer heeft 0 punten.
Maak een keuze:
1 voor schaar
2 voor steen
3 voor papier
> 1
De computer kiest papier!
Jij wint deze ronde!
Jij hebt 1 punt, de computer heeft 0 punten.
Maak een keuze:
1 voor schaar
2 voor steen
3 voor papier
> 1
De computer kiest steen!
De computer wint deze ronde!
Jij hebt 1 punt, de computer heeft 1 punt.
Jij hebt 10 punten, de computer heeft 8 punten.
Jij bent gewonnen!
Jij hebt 8 punten, de computer heeft 10 punten.
De computer is gewonnen!
string[,] oxoRooster = new string[3,3];
string[,] oxoRooster = {{null,null,null},
                        {null,null,null},
                        {null,null,null}}
string[,] oxoRooster = {{"o","o","o"},
                        {"x","o","x"},
                        {"o","x","x"}}
// toont het vakje in het midden (tweede rij, tweede kolom)
Console.WriteLine(oxoRooster[1,1]);
// toont het vakje rechtsonder (derde rij, derde kolom)
Console.WriteLine(oxoRooster[2,2]);
// toont het vakje onderaan in het midden (derde rij, tweede kolom)
Console.WriteLine(oxoRooster[2,1]);
int arrayRijen = oxoRooster.GetLength(0);
int arrayKolommen = oxoRooster.GetLength(1);
int arrayDimensions = oxoRooster.Rank;
public static void PrintRij(int rij, string[,] array) {
	for(int i = 0; i < array.getLength(1); i++) {
		Console.Write(array[rij,i]);
	}
	Console.WriteLine();
}

// ergens anders in de code
for(int rij = 0; rij < oxoRooster.GetLength(0); rij++) {
    PrintRij(rij,oxoRooster);
}
// na elementen met index 2, 5 en 8 moeten we een newline printen
for(int i = 0; i < oxoArray.Length; i++) {
    Console.Write(oxoArray[i]);
    // dit omvat meer rekenwerk
    // 3 is hier ook "hardgecodeerd" als lengte van een rij
    // dus als we de spelregels wijzigen (bv. OXOXO), werkt deze code niet meer
    if((i + 1) % 3 == 0) {
        Console.WriteLine();
    }
}
// per rij doorlopen we de kolommen en printen we een newline
for(int rij = 0; rij < oxoRooster.GetLength(0); rij++) {
    // voor een kolom tonen we gewoon alle waarden
    for(int kolom = 0; kolom < oxoRooster.GetLength(1); kolom++) {
        Console.Write(oxoRooster[rij,kolom]);
    }
    Console.WriteLine();
}
public static void PrintRij(int rij, string[,] array, int maxI) {
	for(int i = 0; i < array.getLength(1) && i <= maxI; i++) {
		Console.Write(array[rij,i]);
	}
	Console.WriteLine();
}

for(int rij = 0; rij < oxoRooster.GetLength(0); rij++) {
    PrintRij(rij,oxoRooster,rij); // print bv. 3 karakters van rij 3, 4 karakters van rij 4,...
}
for(int rij = 0; rij < oxoRooster.GetLength(0); rij++) {
    for(int kolom = 0; kolom < oxoRooster.GetLength(1) && kolom <= rij; kolom++) {
        Console.Write(oxoRooster[rij,kolom]);
    }
    Console.WriteLine();
}
1 
2 2 
3 3 3 
4 4 4 4 
5 5 5 5 5 
for (int i = 1; i <= getal; i++) {
  Console.Write($"{getal} ");
}
for (int i = 1; i <= anderGetal; i++) {
  Console.WriteLine();
}
for (int i = 1; i <= anderGetal; i++) {
  for (int j = 1; j <= i; j++) {
    Console.Write($"{i} ");
  }
  Console.WriteLine();
}

Enumeraties: nog een eigen datatype

Enum datatypes

Met klassen kan je dus complexe concepten zoals een lening modelleren in code. Soms hoeft het niet zo complex, maar is het toch nuttig om wat meer structuur te voorzien dan met heel brede datatypes zoals int en string. Met een string kan je bijvoorbeeld om het even welke tekst voorstellen, dus oneindig veel mogelijkheden. Met een byte kan je een getal tussen 0 en 255 voorstellen. Met de andere getaltypes kan je een getal in een ander bereik voorstellen. Soms matchen de waarden die je kan voorstellen niet met de mogelijkheden in het probleemdomein.

Als je de mogelijkheden in het probleemdomein op voorhand kan vastleggen, is het vaak handig gebruik te maken van een enumeratie (enum). Een enumeratie is een opsomming van alle mogelijke waarden voor een variabele van een bepaald type.

Een voorbeeld: weekdagen

Stel dat je een programma moet schrijven dat afhankelijk van de dag van de week iets anders moet doen. Zonder enums zou je dit kunnen schrijven op 2 zeer foutgevoelige manieren:

  1. Met een int die een getal van 1 tot en met 7 kan bevatten

  2. Met een string die de naam van de dag bevat

Beide hebben tekortkomingen, omdat ze niet goed genoeg overeenstemmen met de mogelijkheden in de werkelijkheid.

Slechte oplossing 1: Met ints

De waarde van de dag staat in een variabele int dagKeuze. We bewaren er 1 in voor Maandag, 2 voor dinsdag, enzovoort.

if(dagKeuze==1)
{
    Console.WriteLine("We doen de maandag dingen");
}
else 
if (dagKeuze==2)
{
    Console.WritLine("We doen de dinsdag dingen");
}
else 
if //enz..

Deze oplossing heeft 2 grote nadelen:

  • Wat als we per ongeluk dagKeuze een niet geldige waarde geven, zoals 9, 2000, -4, etc. ? We kunnen daar wel conditionele code voor voorzien, maar de compiler zal ons niet waarschuwen als we dat vergeten!

  • De code is niet erg leesbaar. dagKeuze==2? Was dat nu maandag, dinsdag of woensdag (want misschien beginnen we te tellen vanaf zondag, of misschien beginnen we te tellen vanaf 0). Je kan wel afspraken maken binnen je team, maar het is altijd mogelijk een afspraak te vergeten.

Slechte oplossing 2: Met strings

De waarde van de dag bewaren we nu in een variabele string dagKeuze. We bewaren de dagen als "maandag", "dinsdag", etc.

if(dagKeuze=="maandag")
{
    Console.WriteLine("We doen de maandag dingen");
}
else 
if (dagKeuze=="dinsdag")
{
    Console.WritLine("We doen de dinsdag dingen");
}
else 
if //enz..

De code wordt nu wel leesbaarder dan met 1, maar toch is ook hier 1 groot nadeel:

  • De code is veel foutgevoeliger voor typfouten. Wanneer je "Maandag" i.p.v. "maandag" bewaart dan zal de if al niet werken. Iedere schrijffout of variant zal falen.

Enumeraties: het beste van beide werelden

Het keyword enum geeft aan dat we een nieuw datatype maken. Gelijkaardig aan wat we voor een klasse doen, dus. Wanneer we dit nieuwe type hebben gedefinieerd kunnen we dan ook variabelen van dit nieuwe type aanmaken (dat is waarom we spreken van een "datatype"). Anders dan bij een klasse beperken we tot alleen de opgesomde opties.

In C# zitten al veel enum-types ingebouwd. Denk maar aan ConsoleColor: wanneer je de kleur van het lettertype van de console wilt veranderen gebruiken we een enum-type. Er werd reeds gedefinieerd wat de toegelaten waarden zijn, bijvoorbeeld: Console.ForegroundColor = ConsoleColor.Red;

Achter de schermen worden waarden van een enum type nog steeds voorgesteld als getallen. Maar je gebruikt deze getallen niet rechtstreeks en dat verkleint de risico's.

Zelf enum maken

Zelf een enum type gebruiken gebeurt in 2 stappen:

  1. Het type en de mogelijke waarden definiëren

  2. Variabele(n) van het nieuwe type aanmaken en gebruiken in je code

Stap 1: het type definiëren

We maken eerst een enum type aan. Wij zullen dit doen in een aparte file met dezelfde naam als het nieuwe datatype. Bijvoorbeeld:

namespace Programmeren {
    enum Weekdagen {Maandag, Dinsdag, Woensdag, Donderdag, Vrijdag, Zaterdag, Zondag}
}

Vanaf nu kan je variabelen van het type Weekdagen aanmaken. Merk op dat er geen puntkomma voorkomt. De syntax is ongeveer dezelfde als die voor de definitie van een klasse.

Stap 2: variabelen van het type aanmaken en gebruiken.

We kunnen nu variabelen van het type Weekdagen aanmaken. Bijvoorbeeld:

Weekdagen dagKeuze;
Weekdagen andereKeuze;

En vervolgens kunnen we waarden aan deze variabelen toewijzen als volgt:

dagKeuze = Weekdagen.Donderdag;

Kortom: we hebben variabelen zoals we gewoon zijn, het enige verschil is dat we nu beperkt zijn in de waarden die we kunnen toewijzen. Deze kunnen enkel de waarden zijn die in het type gedefinieerd werden. De code is nu ook een pak leesbaarder geworden.

Waarden van een enum type inlezen

Eens je met enums aan het werken bent in je programma, kan je gerust zijn dat deze een geldige waarde bevatten. Er is alleen het probleem dat je soms een van de mogelijkheden moet krijgen van de gebruiker. Een waarde van een enum type rechtstreeks intypen gaat niet. Net zoals wanneer je een getal inleest, is er een omzetting nodig. Het verschil is dat we hier een dubbele omzetting doen: van tekst naar getal en dan van getal naar enum waarde.

Je kan dit als volgt doen:

Weekdagen keuze;
Console.WriteLine("Welke dag is het vandaag?");
Console.WriteLine($"{(int) Weekdagen.Maandag}. {Weekdagen.Maandag}");
Console.WriteLine($"{(int) Weekdagen.Dinsdag}. {Weekdagen.Dinsdag}");
// enzovoort
keuze = (Weekdagen) Convert.ToInt32(Console.ReadLine());

Dit werkt en je hoeft de achterliggende getallen voor de waarden niet te kennen. Er zijn meer geavanceerde mogelijkheden, maar dit volstaat op dit punt.

hier opgelijst
unicode-table.com
hier

Oefeningen

Inleiding

Al deze oefeningen maak je als statische methoden van een klasse Meerdimensionaal. Je kan elk van deze methoden uitvoeren via een keuzemenu, zoals bij vorige hoofdstukken.

H09-Som-n-de-rij

Leerdoelen

  • Werken met multidimensionale arrays

Functionele analyse

We wensen de gegevens in één rij te groeperen door hun som te bepalen, een beetje zoals je dat in een Excel spreadsheet zou kunnen doen.

Technische analyse

Schrijf in de klasse Meerdimensionaal een methode SomNdeRij. Deze heeft twee parameters: een meerdimensionale array van double en een rijnummer (een int). De returnwaarde is de som van alle waarden op de rij met het gevraagde nummer.

Deze methode toont niets op de console, maar je kan ze wel testen via deze voorbeeldcode, die je mag gebruiken wanneer deze oefening wordt opgestart via het keuzemenu:

double[,] numbers = {{4.2, 8.1, 3.3},
                     {2.0, 4.0, 6.0},
                     {3.1,3.2,3.3}};
Console.WriteLine("Van welke rij wil je de som zien?");
int row = Convert.ToInt32(Console.ReadLine());
Console.WriteLine(SomNdeRij(numbers,row));

H09-Som-per-rij

Leerdoelen

  • Werken met multidimensionale arrays

Functionele analyse

We wensen de gegevens in één rij te groeperen door hun som te bepalen, een beetje zoals je dat in een Excel spreadsheet zou kunnen doen.

Technische analyse

Schrijf in de klasse Meerdimensionaal een methode SomPerRij. Deze maakt een tweedimensionale array met het gevraagde aantal rijen en het gevraagde aantal kolommen aan. Je mag veronderstellen dat er een geldig aantal wordt ingegeven. Vervolgens vraagt ze, rij per rij en kolom per kolom, een getalwaarde. Wanneer elke positie is ingevuld, toont ze de som per rij van de ingegeven getallen.

Voorbeeldinteractie

Hoe veel rijen telt je array?
> 3
Hoe veel kolommen telt je array?
> 2
Waarde voor rij 1, kolom 1?
> 4
Waarde voor rij 1, kolom 2?
> 2
Waarde voor rij 2, kolom 1?
> 1
Waarde voor rij 2, kolom 2?
> 1
Waarde voor rij 3, kolom 1?
> 7
Waarde voor rij 3, kolom 2?
> 9
Sommen per rij:
6
2
16

H09-Pixels

Leerdoelen

  • Werken met multidimensionale arrays

  • Werken met enumeratietypes

Functionele analyse

We willen een simpel tekenprogramma maken in de terminal. De gebruiker kan pixel per pixel een gewenste kleur aangeven.

Technische analyse

Schrijf in Meerdimensionaal een methode Pixels.

Vraag hierin eerst aan de gebruiker welke afmetingen hij wil gebruiken voor zijn afbeelding. Dit bepaalt het aantal rijen en kolommen en dus het aantal pixels. Maak vervolgens een array van ConsoleColor waarden aan met deze afmetingen. Vraag tenslotte in een lus wat de gebruiker wil doen:

  • een pixel kleuren

    • vraag hierbij de rij-index en kolom-index

    • vraag ten slotte in welke kleur deze moet worden ingevuld

      • je kan sneller een array van alle kleuren krijgen met volgende code: ConsoleColor[] kleuren = (ConsoleColor[]) Enum.GetValues(typeof(ConsoleColor));

      • je hoeft deze instructie niet volledig te begrijpen: ze doet hetzelfde als ConsoleColor[] kleuren = {ConsoleColor.Back, ConsoleColor.DarkBlue, ...} maar vraagt gewoon minder typwerk

  • de afbeelding zoals ze momenteel is tonen

    • toon hiervoor elke pixel als een spatie met Console.Write(" ")

Voorbeeldinteractie

H09-color-filter

Leerdoelen

  • Werken met multidimensionale arrays

  • Werken met enumeratietypes

Functionele analyse

We zullen ons tekenprogramma uitbreiden met een extra functie die de array bewerkt. Met andere woorden, een (simpele) afbeeldingsfilter. Deze zal bepaalde pixels automatisch rood kleuren.

Technische analyse

Schrijf een functie RedFilter. Deze heeft een afbeelding (een ConsoleColor[,]) als invoer en vervangt alle vakjes die zich op en oneven rij én een oneven kolom bevinden door de kleur rood. Ze toont de afbeelding niet.

Deze oefening kan je niet rechtstreeks oproepen via het keuzemenu voor deze klasse. Het wordt een extra optie om iets met je afbeelding te doen.

Voorbeeldinteractie

Wat wil je doen?
1. een pixel kleuren
2. afbeelding tonen
3. roodfilter toepassen

H09-triangle-filter

Leerdoelen

  • Werken met multidimensionale arrays

  • Werken onderling afhankelijke indexen

Functionele analyse

We zullen ons tekenprogramma uitbreiden met nog een extra functie die de array bewerkt. Deze functie "knipt" de afbeelding diagonaal in twee.

Technische analyse

Schrijf een functie LowerTriangleFilter. Deze heeft een afbeelding (een ConsoleColor[,]). Ze zorgt ervoor dat alle pixels op of onder de diagonaal van links boven tot rechts onder wit worden gemaakt. Dit vereist wat te veel rekenwerk als de afbeelding geen vierkant is, dus geef de foutmelding "deze filter kan niet worden toegepast op deze afbeelding" als het aantal rijen verschilt van het aantal kolommen.

Deze oefening kan je niet rechtstreeks oproepen via het keuzemenu voor deze klasse. Het wordt een extra optie om iets met je afbeelding te doen.

Voorbeeldinteractie

Wat wil je doen?
1. een pixel kleuren
2. afbeelding tonen
3. roodfilter toepassen
4. driehoeksfilter toepassen

H09-HeatmapPaardensprong

Leerdoelen

  • Werken met multidimensionale arrays

  • Werken met geneste iteratie

  • Werken met Random

Functionele analyse

Ik wil een paard een aantal willekeurige (random) sprongen laten maken op een schaakbord vertrekkende van een gegeven positie.

Na de sprongen wil ik een "heatmap" van deze sprongen. Hoe vaak is het paard op een positie geweest.

Volg de conventie:

Technische analyse

Schrijf een nieuwe klasse HeatmapPaardensprong. Het startpunt in deze klasse is de methode HeatmapPaardensprongMain.

Vraag hierin eerst aan de gebruiker hoe vaak het paard moet springen en vraag ook de beginpositie in de vorm A1..H8

Je gaat vervolgens vertrekkende van deze positie een aantal willekeurige sprongen kiezen. Per sprong zijn er maximaal 8 mogelijkheden (zie afbeelding). Je zorgt er voor dat de sprong geldig is (binnen het bord). Deze positie is de startpositie voor de volgende sprong. De positie zelf is ook bezocht, dat houd je bij.

Voorbeeldinteractie

H09-ConwayGameOfLife

Leerdoelen

  • Werken met multidimensionale arrays

  • Werken met geneste iteratie

  • Werken met Random

Functionele analyse

Technische analyse

Schrijf een nieuwe klasse ConwayGameOfLife. Het startpunt in deze klasse is de methode ConwayGameOfLifeMain.

Vraag hierin eerst aan de gebruiker hoe veel cellen hij wil: 10 geeft een veld van 10 op 10 cellen.

Vervolgens vraag je de gebruiker hoeveel generaties hij wil zien.

Je gaat vervolgens het bord willekeurig opvullen. Gebruik een enum type om de status van een cel (levend, dood) bij te houden.

Vervolgens ga je generatie per generatie laten zien. Van de éne generatie op de andere gelden volgende regels:

  • Any live cell with fewer than two live neighbors dies, as if by loneliness

  • Any live cell with more than three live neighbors dies, as if by overcrowding.

  • Any live cell with two or three live neighbors lives, unchanged, to the next generation.

  • Any dead cell with exactly three live neighbors comes to life.

Laat elke generatie 2 seconden zien en bereken dan de volgende generatie (kijk even naar de oefening met de chronometer!).

Voorbeeldinteractie

OOP Intro

OOP

Object Oriented Programming of korter OOP is een techniek afkomstig van higher level programmeertalen zoals Java, C#, VB.NET, ... en vindt zijn oorsprong bij de programmeertaal Smalltalk, die het eerst de term Object Oriented Programming introduceerde.

In recentere jaren heeft deze techniek echter ook zijn weg gevonden naar scripting talen zoals Python, Ruby, Perl en zelfs PHP.

OOP streeft ernaar om een project zo structureel mogelijk op te bouwen in objecten. Dit heeft voor de programmeur het grote voordeel dat code vanaf nu in logische componenten wordt opgedeeld en veel makkelijker te hergebruiken is.

Om het concept van objecten te illustreren wordt vaak een voorwerp uit het dagelijks leven als voorbeeld gebruikt. Neem bijvoorbeeld een auto. De auto is het object en dit object heeft zowel geassocieerde data als functionaliteit. Deze data stellen eigenschappen of onderdelen van het object voor. Een eigenschap van de auto kan de kleur, de lengte, het aantal kilometers op de teller, of zelfs zijn huidige locatie of snelheid zijn. Deze zaken worden geïmplementeerd met ofwel instantievariabelen, ofwel properties. De functionaliteit is iets dat het object kan doen. Deze wordt geïmplementeerd met instantiemethodes. Als je een auto als object ziet, kunnen deze bijvoorbeeld starten, versnellen of remmen zijn.

Auto's zijn een veelgebruikt voorbeeld, maar eigenlijk schrijf je er zelden objecten voor tenzij je een soort simulator wil maken. Objecten hoeven niet altijd tastbare zaken te zijn. Je kan bijvoorbeeld ook een lening bij de bank voorstellen als een object. Dit object heeft dan onder andere volgende eigenschappen:

  • een begunstigde, d.w.z. de persoon die de lening heeft aangegaan

  • een restsaldo, d.w.z. het bedrag dat je moet betalen

  • een looptijd, d.w.z. de periode waarbinnen de lening afbetaald moet zijn

  • een intrestvoet, die uiteindelijk bepaalt hoe veel je bovenop het oorspronkelijke bedrag betaalt

Maar met een lening kunnen we ook bepaalde zaken doen die we wel in een computersysteem zouden implementeren. Deze zaken vormen de functionaliteit, dus de verzameling instantiemethodes, van het lening-object:

  • een afbetaling verwerken

  • de uiteindelijk af te betalen som berekenen

  • het afbetaald kapitaal en de afbetaalde interest na een bepaald aantal maanden berekenen

Dus voor zaken die (minstens) zo uiteenlopend zijn als een auto en een lening hebben we een combinatie van data en functionaliteit. Zo zit een object in een programmeertaal er ook uit.

Black-box principe

Een belangrijk concept bij OOP is het black-box principe waarbij we de afzonderlijke objecten en hun werking als "zwarte dozen" gaan beschouwen. Dat wil zeggen dat we (meestal) niet gaan kijken hoe ze in elkaar zitten. Neem het voorbeeld van de auto: deze is in de echte wereld ontwikkeld volgens het blackbox-principe. De werking van de auto kennen tot in het kleinste detail is niet nodig om met een auto te kunnen rijden. De auto biedt een aantal zaken aan de buitenwereld aan (het stuur, pedalen, het dashboard), wat we de publieke interface of gewoonweg interface noemen. Deze zaken kan je gebruiken om de interne staat van de auto uit te lezen of te manipuleren. Stel je voor dat je moest weten hoe een auto volledig werkte voor je ermee op de baan kon.

Binnen OOP wordt dit blackbox-concept encapsulatie genoemd. Het doel van OOP is andere programmeurs (en jezelf) zoveel mogelijk af te schermen van de interne werking van je code. Net als bij een auto, moet je alleen weten wat elk stukje van de interface klaarspeelt, maar hoef je niet te weten hoe dit allemaal is geprogrammeerd.

Encapsulatie wordt vaak een van de vier pijlers van objectgeoriënteerd programmeren genoemd. De andere zijn abstractie, overerving en polymorfisme. Deze komen allemaal later in de cursus aan bod.

Klassen en objecten

Een elementair aspect binnen OOP is het verschil beheersen tussen een klasse en een object.

Wanneer we meerdere objecten gebruiken van dezelfde soort dan kunnen we zeggen dat deze objecten allemaal deel uitmaken van een zelfde klasse. Zo hebben we bijvoorbeeld de klasse van de leningen. Er zijn leningen van €50.000, er zijn leningen van €10.000.000. Er zijn er met een intrestvoet van 0.2% en er zijn er met een intrestvoet van 2%. Er zijn er met een looptijd van 5 jaar en er zijn er met een looptijd van 20 jaar. Het zijn allemaal leningen, maar ze hebben allemaal andere eigenschappen.

Als je vertrouwd bent met relationele databanken: een klasse is vergelijkbaar met een tabel of entiteittype, terwijl een object vergelijkbaar is met een record of een entiteit.

Objecten van dezelfde soort volgen wel dezelfde regels en hebben in dat opzicht dezelfde functionaliteit. Maar de eigenschappen van object bepalen ook het resultaat van de instantiemethodes. Eerder zagen we dat de uiteindelijk af te betalen som berekend kan worden door een lening. Deze som zal per definitie hoger zijn van een lening met een hoge rentevoet dan voor een lening met een lage rentevoet (als de andere eigenschappen gelijk zijn).

Samengevat:

  • Een klasse is een beschrijving en verzameling van dingen (objecten) met soortgelijke eigenschappen en gedrag.

  • Een individueel object is een instantie (exemplaar, voorbeeld, verschijningsvorm) van een klasse

Afsluiter: TextCell2D

Inleiding

In deze laatste les zullen we nog een waardevolle verbetering toevoegen aan het TextCell project. Meerbepaald zullen we er voor zorgen dat we rijen en kolommen hebben, zoals een echte spreadsheet.

We starten hierbij vanaf de code die we vorige keer hebben opgebouwd.

Tweedimensionaal werken

Om deze uitbreiding te doen, onthouden we best dat een tweedimensionale array een uitbreiding is op een ééndimensionale. Je denkt altijd best na over wat er gebeurt los van de code en doet dan pas de aanpassing.

Schematische weergave

Bekeken van uit het perspectief van de eindgebruiker:

Bekeken uit het perspectief van de programmeur:

Aanpassing TextCellMain

Bij het aanmaken van het rooster, vragen we nu hoe veel rijen en hoe veel kolommen het rooster zal tellen in plaats van hoe veel cellen. We vullen deze ook allemaal op met lege stringwaarden.

Aanpassing WijzigCel

In plaats van te vragen welke cel je wil wijzigen (als lettercode), vraag je nu welke rij (als getalcode) en welke kolom (nog steeds als lettercode) je wil wijzigen.

Aanpassing BerekenEnToonRooster

Het is de bedoeling dat deze methode een berekende weergave van ons achterliggend rooster bepaalt. Deze berekende weergave heeft dezelfde afmetingen als ons achterliggend rooster. Verder moeten we nu meerdere niet één rij tonen, maar meerdere rijen. We nummeren de rijen voortaan ook in de visuele weergave.

Aanpassing KopieerCellenZonderFormule

Het idee blijft hetzelfde: als een cel een letterlijke waarde bevat, kunnen we ze meteen overnemen in de overeenkomstige cel van de uitgerekende versie van het rooster. We moeten alleen rekening houden met het feit dat we nu niet één rij kopiëren, maar meerdere rijen.

Aanpassing BevatNullWaarden

Het idee blijft hetzelfde: we doorlopen alle cellen. Als we er een tegenkomen die null bevat, is het antwoord meteen true.

Aanpassing BerekenOntbrekendeWaardenEenKeer

Zowel ons inputrooster als ons outputrooster zijn nu tweedimensionaal. We moeten dus meerdere rijen doorlopen in plaats van één rij, maar het idee blijft hetzelfde: als we een cel tegenkomen waarvan we de waarde nog niet eerder konden invullen, bekijken zullen we bekijken of we de formule intussen kunnen uitrekenen.

Aanpassing BerekenOntbrekendeCelRechtstreeks

We splitsen een som nog steeds door op zoek te gaan naar de plustekens. Het enige verschil is dat ons berekend rooster van type verandert.

Aanpassing WaardeVanCel

Deze methode diende om een symbolenreeks te vertalen naar een (tekstvoorstelling van een) getal. Deze symbolenreeks kon al een getal voorstellen, of het kon een verwijzing naar een andere cel zijn. Deze verwijzingen zijn iets complexer geworden. Eerder was het gewoon een (reeks) letter(s) die een kolom aanduidde, zoals A of B. Die werden dan vertaald naar 1 of 2 en dan werd het zoveelste element van de ééndimensionale array opgezocht. Nu is een verwijzing een reeks letters (voor de kolom) gevolgd door een reeks cijfers (voor de rij). Om deze aanpassing te doen, moeten we de twee delen van elkaar scheiden. We kunnen dat doen door te kijken waar in de celnaam we het eerste cijfer tegenkomen. Eens we de rij en kolom hebben in een gebruiksvriendelijke notatie, zetten we ze om naar array indexen.

Outro

Onze spreadsheet ziet er al een stuk nuttiger uit. Zoals eerder gezegd vormt een uitbreiding op dit project een oefening van het examen. Zorg dus dat je de code hebt, maar ook dat je de structuur ervan goed begrijpt. Je kan je ook voorbereiden door zelf wat uitbreidingen te verzinnen en deze in aangepaste kopieën van je klasse te schrijven. Enkele goede oefeningen voor jezelf:

  • zorgen dat rijnummers mooi blijven weergeven boven 2 cijfers (door eerst uit te rekenen hoe veel cijfers de laatste rij nodig heeft)

  • zorgen dat je kolommen vanzelf breed genoeg zijn om de breedste waarde in die kolom volledig voor te stellen (door eerst alle waarden in de kolom te doorlopen)

  • positieve getalwaarden groen kleuren en negatieve getalwaarden rood kleuren

  • niet alleen sommen, maar ook vermenigvuldigingen ondersteunen

  • je spreadsheet omzetten naar een CSV-bestand (dat je dan zou kunnen openen in een volwaardige spreadsheet editor)

  • hoofdingen en/of content automatisch centreren of rechts uitlijnen

Klassen en objecten aanmaken

In C# kunnen we geen objecten aanmaken voor we een klasse hebben gedefinieerd die de algemene eigenschappen (properties) en werking (methoden) beschrijft.

Klasse maken

Een heel elementaire klasse heeft de volgende vorm:

class ClassName
{
    // hier komen de data en functionaliteit
}

We maken hier normaal gebruik van Pascal case. Je kan ook nog enkele toevoegingen doen die niet zuiver data en functionaliteit zijn, maar die houden we voor verder in de cursus.

Volgende code beschrijft de klasse Auto in C#

class Auto
{
    // data kan bv. nummerplaat, bouwjaar of kilometerstand zijn
    // gedrag kan bv. bepalen van de verkoopwaarde zijn
}

Binnen het codeblock dat bij deze klasse hoort zullen we verderop dan de werking beschrijven.

Klassen in Visual Studio

Je kan "eender waar" een klasse aanmaken, maar het is een goede gewoonte om per klasse een apart bestand te gebruiken. Dit is ook de afspraak die wij zullen volgen.

  • In de solution explorer, rechterklik op je project

  • Kies Add

  • Kies Class...

  • Geef een goede naam voor je klasse

De naam van je klasse moet voldoen aan de identifier regels die ook gelden voor het aanmaken van variabelen!

Objecten aanmaken

Je kan nu objecten aanmaken van de klasse die je hebt gedefinieerd. De klasse is een type en je kan dus ook variabelen aanmaken die bedoeld zijn om objecten van deze klasse in bij te houden. Je doet dit door eerst een variabele te definiëren met als type de naam van de klasse en vervolgens een object te instantiëren met behulp van het new keyword:

Auto mijnEerste = new Auto();
Auto mijnAndereAuto = new Auto();

We hebben nu twee objecten aangemaakt van het type Auto.

Let goed op dat je dus op de juiste plekken dit alles doet (bekijk de onderstaande screenshot):

  • Klassen maak je aan als aparte files in je project

  • Objecten creëer je in je code op de plekken dat je deze nodig hebt, bijvoorbeeld in je Main methode bij een Console-applicatie

Je hebt dus in het verleden ook al objecten aangemaakt met new. Telkens je met Random werkt deed je dit al. Dit wil zeggen dat er dus in .NET ergens reeds een voorgeprogrammeerde klasse Random bestaat met de interne werking.

Oefeningen

Inleiding

Al deze oefeningen maak je als statische methoden van een klasse GevorderdeTekstverwerking. Je kan elk van deze methoden uitvoeren via een keuzemenu, zoals bij vorige hoofdstukken.

H10-Som-van-getallen

Leerdoelen

  • Werken met .Split en .Join

  • Itereren over array

Functionele analyse

Je vraagt de gebruiker een aantal getallen gescheiden door ';' in te geven. je laat vervolgens de som van deze getallen zien.

Technische analyse

Schrijf in de klasse GevorderdeTekstverwerkingeen methode SomVanGetallen. Deze vraagt de gebruiker een aantal getallen te geven gescheiden door een ';'. Je leest de invoer in, splitst de invoer in getallen, maakt de som en laat die zien. Let op, het aantal getallen mag de gebruiker zelf bepalen.

Voorbeeldinteractie

H10-Centraal-Aligneren-Tekst

Leerdoelen

  • Formateren van tekst

  • String interpolatie

  • Methode oproep

Functionele analyse

Je vraagt de gebruiker een tekst in te geven. Vervolgens vraag je de gewenst breedte van de tekst. Je laat de tekst dan centraal gealigneerd aan de gebruiker zien.

Technische analyse

Schrijf in de klasse GevorderdeTekstverwerkingeen methode CentraalAlignerenTekst. Deze vraagt de gebruiker een tekst in te geven en een gewenste lengte (minimaal de lengte van de tekst - controle!). Maak een tweede methode CentraalAligneren Vervolgens wordt de tekst centraal gealigneerd getoond aan de gebruiker. De gebruiker kan het padding karakter ook kiezen.

Voorbeeldinteractie

H10-KerstinkopenNetjes

Leerdoelen

  • stringformattering

  • string methodes

  • gebruik van karakters

Functionele analyse

Technische analyse

Maak een kopie van Kerstinkopen in deze klasse.

Pas door middel van stringformattering en string methodes de code aan, zodat:

  • Een scheidingslijn getekend wordt onder "Info over je aankopen", die net breed genoeg is. Als we later deze hoofding veranderen (bijvoorbeeld naar "Informatie over je aankopen") moet de lijn mee groeien of krimpen.

  • Alle bedragen netjes onder elkaar staan in één kolom. Hiervoor mag je veronderstellen dat de berichten op de linkerkant maximum 25 karakters in beslag zullen nemen.

  • Het symbool voor de munteenheid van de gebruiker vanzelf gebruikt wordt. Dit kan dus € zijn maar ook $ of £ of iets anders.

Als je het symbool voor de munteenheid niet te zien krijgt in de terminal van Visual Studio, ligt dat niet aan jouw code. Voer je code dan uit via het commando dotnet run in Git bash.

Voorbeeldinteractie

Deze voorbeeldinteractie gebruikt de List versie van Kerstinkopen. Maak de oefening met de array versie. Het gaat hem hier over de weergave onderaan en de aanpassingen daar zijn dezelfde voor beide versies.

H10-TextCellPersistent

Leerdoelen

  • Gebruik van input en output van tekstbestanden

Functionele analyse

We willen onze gemaakte TextCells ook kunnen opslaan en weer openen.

Technische analyse

Maak in je oefeningen map een nieuwe map aan TextCell. In deze map zullen de TextCell bestanden bewaard worden. Je kan in je applicatie ook een relatief pad opgeven (dus niet c:\...). Dit relatief pad gaat vertrekkende van de map waar je nu op werkt een map zoeken (dit is wat kort door de bocht, de werkelijkheid is een stuk ingewikkelder maar voor nu volstaat dit). Het relatieve pad dat wij gebruiken is "./TextCell/filenaam.aptx).

Kopieer het bestand TextCell.cs naar TextCellPersistent.cs. Maak gerbuik van de in de theorie aangehaalde methodes File.ReadAllLines en File.WriteAllLines. Test bij opslaan van het bestand of het bestand reeds bestaat en bij laden van het bestand of het bestand sowieso bestaat.

Voorbeeldinteractie

H10-Pixels-Persistent

Functionele analyse

We wensen de kunstwerken die we in Pixels tekenen op te slaan en in te laden.

Technische analyse

Kopieer de code voor Pixels naar deze klasse.

Zorg ervoor dat je twee extra opties hebt in je tekenprogramma. Om een tekening op te slaan, moet je elke ConsoleColor casten naar een int. Dan kan je één rij pixels opslaan als één rij getallen, gescheiden door puntkomma. Om een tekening in te laden, doe je het omgekeerde.

Voorbeeldinteractie

DateTime: leren werken met objecten

DateTime

Het .NET gegevenstype DateTime is de ideale manier om te leren werken met objecten. Het is een nuttig en toegankelijk gegevenstype. Je kan er je iets bij voorstellen, maar het is ook een beetje abstract.

Waarom spreken we hier over "gegevenstype" en niet over "klasse"? Omdat klassen in .NET reference types zijn. DateTime is echter een value type, dus technisch gezien is het een "struct" en geen "klasse". Wij zullen zelf geen structs schrijven, maar het verschil met klassen is uiterst klein in C#. Instanties van zowel klassen als structs zijn objecten.

Zegt het verschil tussen value types en reference types je niets meer? Kijk dan terug naar deze pagina.

DateTime objecten aanmaken

Er zijn 2 manieren om DateTime objecten aan te maken:

  1. Door aan de klasse de huidige datum en tijd te vragen via DateTime.Now

  2. Door manueel de datum en tijd in te stellen via de constructor

DateTime.Now

Volgend voorbeeld toont hoe we een object kunnen maken dat de huidige datum tijd van het systeem bevat. Vervolgens printen we dit op het scherm:

DateTime currentTime = DateTime.Now;
Console.WriteLine(currentTime);

DateTime op het begin van regel 1 is het type van de variabele. Dit type drukt uit met wat voor data we te maken hebben. Dit is net dezelfde redenering als bij het gebruik van string in string mijnTekst = "hello world";

Met constructor

Enkele voorbeelden:

DateTime birthday = new DateTime(1982, 3, 18); //year, month, day
DateTime someMomentInTime = new DateTime(2017, 1, 18, 10, 16,34 ); //year, month, day, hour, min, sec

Je hebt eerder al met constructoren gewerkt: herinner je new Random(). Hiermee maakte je eigenlijk een object aan dat willekeurige getallen kon genereren.

DateTime methoden

Ieder DateTime object dat je aanmaakt heeft een hoop nuttige methoden.

Add Methods

Deze methoden kan je gebruiken om een bepaalde aantal dagen, uren, minuten en zo voort aan je huidige object toe te voegen. Al deze methoden geven steeds een nieuw DateTime object terug dat je moet bewaren wil je er iets mee doen:

  • AddDays

  • AddHours

  • AddMilliseconds

  • AddMinutes

  • AddMonths

  • AddSeconds

  • AddTicks

  • AddYears

Een voorbeeld:

DateTime timeNow= DateTime.Now;
DateTime nextWeek= timeNow.AddDays(7);

(voorgaande kan ook in 1 lijn: DateTime nextWeek= DateTime.Now.AddDays(7))

Uiteraard mag je ook een bestaand object overschrijven met het resultaat van deze methoden:

DateTime someTime= new DateTime(2019, 4, 1);
// much later...
someTime = someTime.AddYears(10);
Console.WriteLine(someTime);

DateTime properties

Properties zijn een zeer uniek aspect van C#. Ze geven toegang tot de data van een object, maar op een gecontroleerde manier. We zullen deze nog tot in den treure leren maken.

Enkele nuttige properties van DateTime zijn:

  • Date

  • Day

  • DayOfWeek

  • DayOfYear

  • Hour

  • Millisecond

  • Minute

  • Month

  • Second

  • Ticks

  • TimeOfDay

  • Today

  • UtcNow

  • Year

Properties gebruiken

Alle properties van DateTime zijn read-only. Je kan een bestaande datum dus niet aanpassen (maar je kan wel een nieuwe datum baseren op een bestaande datum).

Een voorbeeld:

DateTime moment = new DateTime(1999, 1, 13, 3, 57, 32, 11);
// Year gets 1999.
int year = moment.Year;
// Month gets 1 (January).
int month = moment.Month;
// Day gets 13.
int day = moment.Day;
// Hour gets 3.
int hour = moment.Hour;
// Minute gets 57.
int minute = moment.Minute;
// Second gets 32.
int second = moment.Second;
// Millisecond gets 11.
int millisecond = moment.Millisecond;

Uiteraard mag je ook deze properties gebruiken om direct naar het scherm te schrijven:

DateTime now = DateTime.Now;
Console.WriteLine($"The current day is {now.DayOfWeek}");

Datum en tijd formatteren

Je hebt een invloed op hoe DateTime objecten naar string worden opgezet. Omzetten naar een string is functionaliteit van het object. Je doet dit dus via een objectmethode. Deze gebruik je zoals de statische methoden van eerder, maar je roept ze op voor een specifiek object.

Je kan bepalen hoe de omzetting naar string moet gebeuren door extra formatter syntax mee te geven. Dit zie je in volgende voorbeeld:

DateTime now = DateTime.Now;
WriteLine(now.ToString("d")); // short date 
WriteLine(now.ToString("D")); // long date
WriteLine(now.ToString("F")); // full date and time
WriteLine(now.ToString("M")); // month and day
WriteLine(now.ToString("o")); // date en time separated by T and time zone at the end
WriteLine(now.ToString("R")); // RFC1123 date and time
WriteLine(now.ToString("t")); // short time
WriteLine(now.ToString("T")); // long time
WriteLine(now.ToString("Y")); // year and month

Custom format

Wil je nog meer controle over de output dan kan je ook zelf je formaat specifieren.

Localized time

De manier waarop DateTime objecten worden getoond (via ToString) is afhankelijk van de landinstellingen van je systeem. Soms wil je dit echter op een andere manier tonen. Je doet dit door mee te geven volgens welke culture de tijd en datum getoond moet worden.

Dit vereist dat je eerst een CultureInfo object aanmaakt en dat je dan meegeeft:

DateTime now = DateTime.Now;
CultureInfo russianCI = new CultureInfo("ru-RU");
Console.WriteLine($"Current time in Russian style is: {now.ToString("F", russianCI)}");

Culture names

Opgelet, enkel indien een specifieke culture op je computer staat geïnstalleerd zal je deze kunnen gebruiken.

CultureInfo is een klasse waar je je misschien al iets minder bij kan voorstellen dan DateTime. Een CultureInfo stelt een aantal afspraken voor een bepaalde cultuur voor. De data is de combinatie van taal en regio. De functionaliteit bestaat er dan in om zaken zoals de munt, het gebruikte formaat voor datums,... in die taal en die regio te produceren. Ook hier geldt dus het black box principe: je kan code schrijven die bijvoorbeeld het juiste datumformaat voor Rusland gebruikt wanneer je zelf niet weet wat dat formaat is.

Static method

Sommige methoden zijn static dat wil zeggen dat je ze enkel rechtstreeks op de klasse kunt aanroepen. Vaak zijn deze methoden hulpmethoden waar de individuele objecten niets aan hebben.

Parsing time

Parsen laat toe dat je strings omzet naar DateTime. Dit is handig als je bijvoorbeeld de gebruiker via ReadLine tijd en datum wilt laten invoeren:

string date_string = "8/11/2016"; //dit zou dus ook door gebruiker kunnen ingetypt zijn
DateTime dt = DateTime.Parse(date_string);
Console.WriteLine(dt);

Zoals je ziet roepen we Parse aan op DateTime en dus niet op een specifiek object. Dit is logisch omdat je geen bestaande datum nodig hebt om een nieuwe datum te parsen.

IsLeapYear

Deze nuttige methode geeft een bool terug om aan te geven het meegegeven object eens schrikkeljaar is of niet:

DateTime today = DateTime.Now;
bool isLeap = DateTime.IsLeapYear(today.Year);
if(isLeap == true) {
    Console.WriteLine("This year is a leap year");
}

Dit is logisch omdat je geen volledige bestaande datum nodig hebt. Je wil gewoon iets zeggen over een jaartal, terwijl een DateTime ook een dag, maand, uur,... heeft. Dus dit heeft niet veel te maken met een specifieke DateTime, maar heeft duidelijk wel met de klasse te maken.

Af en toe is het een kwestie van smaak van de auteurs of een methode statisch is of niet. Maar meestal is er een duidelijke "beste" keuze.

TimeSpan

Je kan DateTime objecten ook bij mekaar optellen en aftrekken. Deze bewerking geeft echter geen DateTime object terug, maar een TimeSpan object. Dit is een object dat dus aangeeft hoe groot het verschil is tussen de 2 DateTime objecten:

DateTime today = DateTime.Today;
DateTime borodino_battle = new DateTime(1812, 9, 7);
TimeSpan diff = today - borodino_battle;
WriteLine("{0} days have passed since the Battle of Borodino.", diff.TotalDays);

Oefening

Klokje

Maak een applicatie die bestaat uit een oneindige loop. De loop zal iedere seconde pauzeren: System.Threading.Thread.Sleep(1000);. Vervolgens wordt het scherm leeg gemaakt en wordt de huidige tijd getoond. Merk op dat ENKEL de tijd wordt getoond, niet de datum.

Verjaardag

Maak een applicatie die aan de gebruiker vraagt op welke dag hij jarig is. Toon vervolgens over hoeveel dagen z'n verjaardag dan zal zijn.

Methoden

Instantiemethoden, ook objectmethoden genoemd, weerspiegelen staan toe om functionaliteit toe te voegen aan objecten van een bepaalde klasse. Soms wordt ook gezegd dat ze "gedrag" van de objecten voorzien. Ze verschillen van statische methoden omdat ze niet alleen gebruik kunnen maken van statische onderdelen van klassen, maar ook van het object waar ze zelf bij horen.

Methoden behoren tot een algemenere categorie onderdelen van objecten genaamd members.

Basisvoorbeelden

We gaan verder met de klasse Auto. We willen bijvoorbeeld een applicatie voor de opvolging van deelauto's (Cambio, Poppy, etc.) schrijven. Er zijn verschillende soorten functionaliteit die je kan koppelen aan één auto:

  • voltanken

  • rijden

  • op onderhoud gaan

  • verkoopsprijs bepalen

Is het de auto die deze zaken doet, of is het een persoon? In werkelijkheid is het natuurlijk dat laatste. Maar de functionaliteit is wel veel sterker gelinkt aan auto's dan aan personen en misschien interesseert de persoon die de handeling uitvoert ons niet eens.

Je doet dit met objectmethoden. Deze lijken erg op static methoden, maar ze hebben toegang tot het object waarop ze zijn toegepast.

Een simpele implementatie van dit gedrag zie je hier:

Bovenstaande code is kort om didactische redenen. Er wordt niet gecontroleerd dat je benzinepeil altijd minstens 0l is, er wordt verondersteld dat de capaciteit van je tank 50l is,...

Voor de duidelijkheid kan je het woordje this toevoegen om het huidige object expliciet voor te stellen. Het wordt sterk aangeraden dat je dit doet. Je code wordt er beter leesbaar door.

Gebruik

Om een objectmethode te gebruiken, hebben we een object nodig. We schrijven dan de naam van het object, gevolgd door een punt en een methodeoproep.

Het gedrag van een object kan afhangen van de waarde van de instantievariabelen. Zo zal de verkoopswaarde van auto1 iets lager liggen dan die van auto2. Dat komt omdat this.Kilometers deel uitmaakt van de berekening van de verkoopsprijs. Ook dit valt onder het principe van encapsulatie: er vindt een berekening plaats "onder de motorkap". We hoeven niet te weten hoe de prijs berekend wordt, elk object weet van zichzelf hoe het de prijs berekent.

static methodes

Een statische methode is een methode die wel bij de klasse hoort, maar niet te maken heeft met een specifiek object van die klasse. We gebruiken terug de euronorm als voorbeeld:

SchoolAdmin project

We breiden onze klasse Student uit zodat ze overeenstemt met volgend diagram:

Toevoegen naamkaartje

Het naamkaartje is een stuk tekst, bestaande uit de naam van de student, gevolgd door (STUDENT).

Toevoegen werkbelasting

Dit is 10u per week per cursus.

Push je vooruitgang naar Gitlab!

Attributen

Attributen, ook velden of instantievariabelen genoemd, zijn stukjes data die je bijhoudt in objecten. Ze stellen informatie voor die deel uitmaakt van een (object van een) klasse. Ze werken zoals de variabelen die je al kent, maar hun scope is een klasse of een object van een klasse, afhankelijk van de vraag of ze static zijn of niet. Door gebruik te maken van attributen, kunnen we stukjes data die samen horen ook samen houden op het niveau van de code. Alle data die samen hoort netjes groeperen en op een gestructureerd toegankelijk maken valt onder het begrip encapsulatie dat reeds eerder aan bod kwam.

Attributen behoren tot een algemenere categorie onderdelen van objecten genaamd members.

Basisvoorbeelden

Een typisch voorbeeld van een klasse is Auto. Er zijn verschillende stukjes data die deel kunnen uitmaken van één auto: de kilometerstand, het benzinepeil, de datum van het laatste onderhoud,...

Een reeds gekende manier om verwante informatie bij te houden is met behulp van "gesynchroniseerde" arrays, d.w.z. arrays die verwante data bijhouden op overeenkomstige posities. Onderstaand voorbeeld toont dit voor auto's:

Als we nu een methode willen uitvoeren die iets doet met alle informatie over een auto (bijvoorbeeld een onderhoudsrapport afprinten), moeten we drie waarden meegeven. Het is ordelijker deze zaken bij te houden in een object van klasse Auto als volgt:

Al deze velden zijn voorlopig public. Dat hoeft niet absoluut, maar het vergemakkelijkt de presentatie. Verdere opties volgen snel. Het is ook een afspraak om publieke velden met een hoofdletter te schrijven. Met deze voorstelling kunnen we dit nu doen:

Je ziet hier al enkele voordelen van encapsulatie:

  • je hoeft niet bij te houden hoe de informatie verspreid is over meerdere plaatsen

  • als we meer informatie over auto's (bv. het oliepeil) in het onderhoudsrapport steken, hoeven we onze calls van PrintOnderhoudsrapport niet aan te passen

    • een kenmerk van goede code is dat wijzigingen typisch geen grote wijzigingen vereisen

Beginwaarden

Nu hebben nieuwe auto's standaard 5 km op de teller staan, enzovoort. Merk op: deze waarde wordt voor elk nieuw object opnieuw berekend. Als je dus twee auto's aanmaakt in je programma, zullen zij beide een verschillende datum van het laatste onderhoud bevatten.

static attributen

Iets dat static is, hoort niet bij de objecten, maar wel bij de hele klasse. Bijvoorbeeld: voor auto's worden de milieunormen na een paar jaar strenger. Er is een vaste set milieunormen en de te halen milieunorm wordt vastgelegd voor alle auto's. Daarom zouden we de milieunorm als volgt kunnen bijhouden:

Herhaal: static betekent niet "onveranderlijk" of "vast". Het betekent dat iets op niveau van de klasse werkt en niet op niveau van de objecten van die klasse.

Opdracht: SchoolAdmin

Deze opdracht maak je tijdens de les. Als je de les niet kan bijwonen, volg je de demonstratie in de kennisclip.

Doelstelling

We willen een programma maken dat ons helpt beheren wie is ingeschreven, welke cijfers behaald zijn, enzovoort. Hiervoor brengen we de concepten en functionaliteit die we gebruiken in kaart en stellen we ze voor in code.

We doen dit in een apart project SchoolAdmin. We gebruiken dus niet het project IndividueleOefeningen. Je volgt om dit project op te starten dezelfde stappen als eerder. Let hierbij goed op dat je dit project niet in een bestaande Git repository maakt. Je merkt direct dat je in een Git repository staat als je de nieuwe map SchoolAdmin opent in VSC en Git bash tussen haakjes (main) of (master) toont. Vraag in dat geval aan je lector om na te kijken waar je het project beter wel zet.

Klasse Student

We maken een klasse student, met publieke attributen voor de naam, geboortedatum, het studentennummer en de gevolgde cursussen. Elke student kan vijf cursussen volgen. We houden ook bij hoe veel studenten er zijn via een attribuut.

In de Main methode maken we enkele studenten. Deze zijn als volgt:

  • Said Aziz, geboren 1 juni 2000, volgt Programmeren en Databanken.

  • Mieke Vermeulen, geboren 1 januari 1998, volgt Communicatie.

Delen via GitLab

Wanneer je de opdracht gevolgd hebt, deel je deze via Gitlab. Je maakt hiervoor een nieuw project aan op Gitlab, maar wel in dezelfde groep als eerder (die met jouw naam).

Negeer de mappen bin en obj in je versiebeheer.

Constructors

Constructors

Werking new operator

Objecten die je aanmaakt komen niet zomaar tot leven. Nieuwe objecten maken we aan met behulp van de new operator zoals we al gezien hebben:

De new operator doet 2 dingen:

  • Hij reserveert ruimte voor dit object in het geheugen

  • Hij zorgt ervoor dat initialisatiecode uitvoert om het object klaar te maken voor gebruik

Via de constructor van een klasse kunnen we code meegeven die moet uitgevoerd worden telkens een nieuw object van dit type wordt aangemaakt.

Constructors lijken erg op methodes. We zullen vaak zeggen dat het speciale methodes zijn, al bestaat daar wat technische discussie over. De constructor wordt in elk geval aangeroepen op een gelijkaardige manier aan een gewone methode. Daarom zetten we ronde haakjes: new Auto().

Soorten constructors

Als programmeur van eigen klassen zijn er 3 opties voor je:

  • Je gebruikt geen (expliciete) constructors: het leven gaat voort zoals het is. Je kunt objecten aanmaken zoals eerder getoond. Achter de schermen gebruik je wel een zogenaamde default constructor.

  • Je hebt enkel een parameterloze constructor nodig. Je kan nog steeds objecten met new Auto() aanmaken, maar je gaat zelf beschrijven wat er moet gebeuren bij de parameterloze constructor.

  • Je wenst gebruik te maken van een of meerdere constructoren met parameters. Hierbij zal je dan extra argumenten kunnen meegeven bij de creatie van een object, bijvoorbeeld: new Auto(25,25000). Dit kan bijvoorbeeld een auto maken met 25l benzine in de tank en 25000km op de teller. De betekenis van de getallen hangt af van hoe je de constructor schrijft.

Constructors zijn soms gratis, soms niet

Een lege default constructor voor je klasse krijg je standaard wanneer je een nieuwe klasse aanmaakt. Je ziet deze niet en kan deze niet aanpassen. Je kan echter daarom altijd objecten met new myClass() aanmaken.

Van zodra je echter beslist om zelf een of meerdere constructors te schrijven zal C# zeggen "Oké, jij je zin, nu doe je alles zelf". De default constructor die je gratis kreeg zal ook niet meer bestaan en heb je die dus nodig dan zal je die dus zelf moeten schrijven!

Default constructor

De default constructor is een constructor die je zelf niet schrijft. Hij heeft nooit parameters.

  • Heeft (zoals alle constructoren) als naam de naam van de klasse zelf

Stel dat we een klasse Auto hebben:

We schrijven nergens code voor de constructor. Dan nog kunnen we new Auto() schrijven, maar dan wordt de auto aangemaakt met 5l in de tank en 0km (de defaultwaarde van int) op de teller.

Parameterloze constructor

We willen telkens een Auto-object wordt aangemaakt dat dit een random aantal kilometers op de teller heeft. Via een parameterloze constructor kunnen we dat oplossen (je kan namelijk alleen expressies gebruiken als initiële waarde).

Eerst schrijven de parameterloze constructor, deze ziet er als volgt uit:

De constructor moet de naam van de klasse hebben, public zijn en geen returntype definiëren.

Vervolgens voegen we de code toe die we nodig hebben:

Telkens we nu een object zouden aanmaken met new Auto() zal deze een willekeurige kilometerstand hebben. Je kan trouwens ook in de constructor een initiële waarde aan benzine geven.

Zelfs als er een letterlijke initiële waarde wordt toegekend, gebeurt dit meestal in de constructor. Het is een kwestie van smaak, maar een constructor dient toch om te initialiseren.

Constructor met parameter(s)

Soms wil je argumenten aan een object meegeven bij creatie. We willen bijvoorbeeld de inhoud van de tank en de kilometerstand meegeven die het object moet hebben bij het aanmaken. Met andere woorden, stel dat we dit willen schrijven:

Als we dit met voorgaande klasse , die enkel een parameterloze (default) constructor heeft, uitvoeren, zal de code een fout geven. C# vindt geen constructor die twee ints als parameter heeft.

Dat was eenvoudig. Maar denk eraan: je hebt een eigen constructor geschreven en dus heeft C# gezegd "ok, je schrijft zelf constructor, trek je plan. Maar de parameterloze versie zal je ook zelf moeten schrijven!" Je kan nu enkel je objecten met new Auto(int benzine, int kilometers) aanmaken. Schrijf je new Auto() dan zal je een error krijgen. Wil je die constructor nog hebben, dan zal je die met de hand moeten schrijven, bijvoorbeeld:

Er is geen grens op het aantal constructoren dat je kan schrijven, als ze maar verschillende parameters hebben.

Wanneer heb ik constructoren nodig?

Tot recent maakten we onze objecten steeds aan met de default constructor. Pas daarna gaven we eventuele properties de juiste waarde. Dat houdt een risico in: er is een periode waarin onze objecten nog niet "af" zijn. In het slechtste geval vergeten we zelfs om de properties in te stellen en krijgen we objecten die misschien ongeldig zijn.

Constructoren helpen dit probleem te voorkomen. Als we één constructor hebben, bijvoorbeeld Auto(int benzine, int kilometers), moeten we die gebruiken. We kunnen dus niet vergeten bijvoorbeeld auto1.Benzine = 25 te schrijven, want we worden gedwongen meteen new Auto(25,20000) te schrijven.

Samengevat: als er eigenschappen zijn die je meteen bij het aanmaken van een object wil instellen, maak er dan parameters van een constructor voor.

Constructor chaining

Als je meerdere overloaded constructoren hebt, hoef je niet in elke constructor alle code voor objectinitialisatie te schrijven. Het sleutelwoordje this biedt ook de mogelijkheid eerst een andere constructor aan te roepen en eventueel andere operaties toe te voegen. Dit heet constructor chaining. In bovenstaand voorbeeld kan je ook dit schrijven:

Access modifiers

Access modifiers bepalen welke code door welke andere code mag worden uitgevoerd of aangepast. We hebben al een aantal dingen public gemaakt bij wijze van demonstratie. Dit is handig om voorbeeldjes te tonen, maar in code van hoge kwaliteit, denk je grondig na voor je dit doet.

Public en private access modifiers

De access modifier geeft aan hoe zichtbaar een bepaald deel van de klasse is voor code buiten de klasse zelf. Wanneer je niet wilt dat "van buitenuit" (bv in Main, terwijl je een andere klasse dan Program schrijft) een bepaalde methode kan aangeroepen worden, dan dien je deze als private in te stellen. Wil je dit net wel dat moet je er expliciet public voor zetten.

Test in de voorgaande klasse Auto eens wat gebeurt wanneer je public verwijdert voor een van de methodes. Inderdaad, je krijgt een foutmelding. Lees deze. Ze zegt dat de methode die je wil oproepen wel bestaat, maar niet gebruikt mag worden. Dat komt omdat je testcode in de klasse Program staat en je methode deel uitmaakt van een andere klasse (meerbepaald Auto).

Als je duidelijk wil maken dat bepaalde code niet van buitenaf aangeroepen kan worden, schrijf dan private in plaats van public. Er zijn nog tussenliggende niveaus waar we later op ingaan en als je geen van beide modifiers schrijft, kan het zijn dat je code op zo'n tussenliggend niveau terecht komt. Als beginnende programmeur maak je er best een gewoonte van duidelijk te kiezen voor public of private.

Reden van private

Waarom zou je bepaalde zaken private maken? Het antwoord is opnieuw encapsulatie.

Neem als voorbeeld de kilometerstand. Het is wettelijk verboden een kilometerstand zomaar aan te passen. Hij mag alleen veranderen doordat er met een auto gereden wordt. Dan is het logisch dat dit ook alleen maar op deze manier kan voor onze auto-objecten. We kunnen dit realiseren door kilometers van public naar private te wijzigen. Dan kunnen we de kilometerstand nog wijzigen, maar enkel door de (publieke) methode Rijden te gebruiken. Als we hem dan nog willen kunnen bekijken (maar niet rechtstreeks wijzigen), kunnen we een extra (publieke!) methode ToonKilometerstand voorzien.

Volgens de conventie maken we dan van de grote "K" in kilometers een kleine "k", omdat publieke members met Pascal case genoteerd worden en private members met camel case.

We kunnen ook methoden private maken. Dit gebeurt niet zo vaak als bij attributen, maar het kan wel. Dit doen we bijvoorbeeld als het gaat om een hulpmethode die binnen de klasse nuttig is, maar buiten de klasse fout gebruikt zou kunnen worden. Een typisch voorbeeld: een stukje code dat letterlijk herhaald wordt in twee of meer verschillende publieke methoden. In plaats van deze code te kopiëren, zonderen we ze af in een hulpmethode. Zo hoeven we eventuele bugfixes,... geen twee keer te doen.

Een studente vroeg in een van de afgelopen jaren: "Kunnen we niet gewoon afspreken dat we van sommige zaken afblijven?" In principe wel. Python doet het min of meer zo. Langs de andere kant: als wij meedelen dat de examenvragen op een publieke website staan en dat je er niet naartoe mag surfen, zou niemand dat dan doen? Private velden aanpassen kan soms een goed idee lijken op korte termijn, maar een project saboteren op langere termijn.

SchoolAdmin project

Privé maken (en hernoemen) Cursussen

Het is moeilijk afspraken te maken over hoe Cursussen gebruikt mag worden. Iemand zou zich altijd kunnen vergissen en een cursus invullen op een positie die niet vrij is, of op een andere vrije positie dan de eerste. Daarom leggen we de regels rond dit attribuut vast in de klasse. We doen dit door over te schakelen op volgende structuur:

In C# schrijven we private members in camel case. Daarom wordt het attribuut hernoemd.

Hierbij gaat RegistreerVoorCursus zelf op zoek naar de eerste vrije positie. Als zo'n positie gevonden wordt, wordt de student ingeschreven voor de cursus.

Push je vooruitgang naar Gitlab!

Klassen en objecten weergeven deel 1

Klassen voorstellen

De data en functionaliteit van een klasse, en ook haar relatie tot andere klassen, wordt vaak voorgesteld in een UML-klassendiagram. Hoe je verschillende klassen met elkaar verbindt, houden we voor iets verderop. Ook enkele andere details volgen later. Wat we wel al kunnen vertellen:

  • elke klasse wordt voorgesteld als een rechthoek, met bovenaan in die rechthoek de naam van de klasse

  • in een tweede vakje worden dan de eigenschappen gegeven, gewoonlijk met hun datatype

  • in een derde vakje worden dan de methoden gegeven, met hun parameters en hun returntype

Het (return) type kan voor de naam van een attribuut of methode staan (zoals in C#), of het kan helemaal achteraan staan, voorafgegaan door een dubbele punt (zoals in TypeScript).

Voorbeeld 1: Lening

Dit vertelt ons dat een object van klasse Lening beschikt over een saldo en een intrestvoet en het vertelt ons ook welke datatypes we gebruiken om deze voor te stellen. Bovendien kan een Lening zelf bepalen hoe veel we in totaal zullen moeten betalen als we elke maand een vast bedrag aflossen. Dit is functionaliteit van het object, die we zullen implementeren met een instantiemethode.

Voorbeeld 2: Auto

Dit vertelt ons dat elke Auto een eigen kilometerstand, bouwjaar en huidige snelheid heeft. Het is mogelijk de Auto te laten starten en om hem te laten versnellen met een geven aantal kilometer per uur.

Objecten voorstellen

Een UML-klassendiagram dient voor... klassen. Als je wil kijken naar objecten, gebruik je een sequentiediagram. We gaan hier nog niet erg diep op in, maar we maken alvast duidelijk dat je op een klassendiagram geen objecten ziet.

Een voorbeeld van een sequentiediagram voor een programma dat twee leningen aanmaakt (bijvoorbeeld een programma in een bank om een simulatie te maken voor een klant):

Het figuurtje met als naam ExterneCode stelt iets voor dat ons eigenlijk niet interesseert. Wat de pijlen en de berichten daarop technisch betekenen, maakt voorlopig ook niet zo uit. Het belangrijkste hier is dat er twee objecten van de klasse Lening zijn. Dit wordt extra in de verf gezet omdat ze twee verschillende namen hebben (lening1 en lening2), maar allebei hetzelfde type na de dubbele punt (Lening). Op een klassediagram ga je nooit twee keer een box voor dezelfde klasse zien.

Properties

Properties zijn een feature van C♯ om de leesbaarheid van code te verhogen. Ze zien er uit zoals attributen, maar werken zoals methoden.

Properties behoren tot een algemenere categorie onderdelen van objecten genaamd members.

Properties

In dit hoofdstuk bespreken we eerst waarom properties nuttig zijn. Vervolgens bespreken we de 2 soorten properties die er bestaan:

  1. Full properties

  2. Auto properties

In een wereld zonder properties

Stel dat we volgende klasse hebben:

Stel nu dat we het benzinepeil van een auto als volgt proberen aanpassen:

Misschien is de eerdere methode TankVol() te beperkt en willen we wel een willekeurige hoeveelheid benzine kunnen toevoegen of verwijderen, zo lang we niet minder dan 0l of meer dan 50l in de tank doen.

Een eerste mogelijkheid is om hier methodes voor te schrijven:

Dit gaat. De methodes zijn public, dus we kunnen ze overal oproepen. Bovendien verhindert de SetBenzine-methode ongeldige waarden. Het nadeel is dat de syntax om deze methodes te gebruiken zwaar is. Met publieke attributen konden we dit doen:

Met de zogenaamde getter en setter moeten we dit doen:

Het lijkt niet zo veel, maar code stapelt zich op doorheen de tijd. Properties lossen dit probleem op. Ze zorgen ervoor dat we kunnen "doen alsof" we publieke velden hebben, maar dezelfde hoeveelheid controle kunnen uitoefenen als met getters en setters.

Full properties

Een full property ziet er als volgt uit:

Dankzij deze code kunnen we nu elders dit doen:

Vergelijk dit met de vorige alinea waar we dit met Get en Set methoden moesten doen. Deze property syntax is veel eenvoudiger in het gebruik.

We zullen de property nu stuk per stuk analyseren:

  • public double Benzine: Merk op dat we Benzine met een hoofdletter schrijven. Vaak wordt gekozen voor dezelfde naam als de variabele die we afschermen (in dit geval benzine met een kleine "b"), maar dan in Pascal case. Dat is niet strikt noodzakelijk. Je zou ook Naft kunnen schrijven. public en double staan er om dezelfde reden als in de oplossing met methodes GetBenzine() en SetBenzine(): we willen deze property buiten de klasse kunnen gebruiken en als we hem opvragen, krijgen we een double.

  • { }: Vervolgens volgen 2 accolades waarbinnen we de werking van de property beschrijven.

  • get {}: indien je wenst dat de property data naar buiten moet sturen, dan schrijven we de get-code. Binnen de accolades van de get schrijven we wat er naar buiten moet gestuurd worden. In dit geval return benzine maar dit mag even goed bijvoorbeeld return 4 of een hele reeks berekeningen zijn. Eigenlijk werkt dit net als de body van een methode en kan je hierin doen wat je in een methode kan doen.

    • We kunnen nu van buitenaf toch de waarde van benzine onrechtstreeks uitlezen via de property en het get-gedeelte: Console.WriteLine(auto.Benzine);

  • set {}: in het set-gedeelte schrijven we de code die we moeten hanteren indien men van buitenuit een waarde aan de property wenst te geven om zo een instantievariabele aan te passen. De waarde die we van buitenuit krijgen (eigenlijk is dit een parameter van een methode) zal altijd in een lokale variabele value worden bewaard. Deze zal van het type van de property zijn. In dit geval dus double, want het type bij Benzine is double. Vervolgens kunnen we value toewijzen aan de interne variabele indien gewenst: benzine=value .

Let goed op dat je in je setter schrijft benzine = value en niet Benzine = value. Dat eerste past de verborgen instantievariabele aan. Dat tweede roept de setter opnieuw op. En opnieuw. En opnieuw. Probeer gerust eens een breakpoint te plaatsen voor de toekenning en dan de debugger te starten als je niet ziet waarom dit een probleem is.

Visual Studio heeft een ingebouwde shortcut om snel een full property, inclusief een bijhorende private dataveld, te schrijven. Typ propfull gevolgd door twee tabs!

Full property met toegangscontrole

De full property Benzine heeft nog steeds het probleem dat we negatieve waarden kunnen toewijzen (via de set) die dan vervolgens zal toegewezen worden aan benzine.

We kunnen in de set code extra controles inbouwen. Als volgt:

Deze code zal het benzinepeil enkel aanpassen als het geldig is en anders stilletjes niets doen. Wat je vaak tegenkomt is throw new ArgumentException($"{value} is geen geldig benzinepeil"). Dit doet je programma crashen, maar legt ook uit waarom. We kunnen de code binnen set (en get) zo complex maken als we zelf willen.

Je kan dus extra controles toevoegen, maar deze hebben alleen zin als je de variabele via de property aanpast. Als je in een methode van de klasse auto benzine met kleine "b" aanpast en niet voorzichtig bent, kan je nog steeds een negatief peil instellen. Daarom wordt aangeraden ook binnen de klasse gebruik te maken van de property, dus zo veel mogelijk Benzine in plaats van benzine te gebruiken.

Property variaties

We zijn niet verplicht om zowel de get en de set code van een property te schrijven.

Write-only property

We kunnen dus enkel benzine een waarde geven, maar niet van buitenuit uitlezen.

Read-only property

Read-only property met private set

Soms gebeurt het dat we van buitenuit enkel de gebruiker de property read-only willen maken. We willen echter intern (in de klasse zelf) nog steeds controleren dat er geen illegale waarden aan private datafields worden gegeven. Op dat moment definieren we een read-only property met een private setter:

Van buitenuit zal enkel code werken die deget-van deze property aanroept: Console.WriteLine(auto.Benzine);. Code die de set van buitenuit nodig heeft zal een fout geven zoals: auto.Benzine=40.

Read-only Get-omvormers

Veel properties bieden toegang tot een achterliggende variabele van hetzelfde type, maar dat is niet verplicht. Je kan ook iets berekenen en dat teruggeven via een getter.

Als we verder gaan met de klasse Auto:

Je hebt iets gelijkaardigs gezien bij DateTime. Daar kan je allerlei stukjes informatie uit opvragen, maar eigenlijk wordt er maar één ding bijgehouden: het aantal "ticks". Dat zijn fracties van seconden sinds een bepaalde startdatum. Als je het uur of het aantal minuten of de maand of de weekdag of nog iets anders opvraagt via een DateTime, wordt deze ter plekke berekend uit het aantal ticks. Zo moet maar één stukje informatie worden bijgehouden, wat leidt tot minder fouten.

Auto properties

Automatische eigenschappen (autoproperties) in C# staan toe om eigenschappen (properties) die enkel een waarde uit een private variabele lezen en schrijven verkort voor te stellen. Ze zorgen dat je al snel properties hebt, maar staan je niet toe complexe berekeningen te doen of waarden te controleren zoals full properties dat wel doen.

Voor Auto kan je bijvoorbeeld schrijven:

Dit maakt achter de schermen een privé-attribuut aan zoals benzine met kleine "b", maar met een een verborgen naam. We kunnen dus niet rechtstreeks aan dat attribuut.

Wij zullen geen gebruik maken van autoproperties, omdat het verschil met publieke attributen pas in meer geavanceerde scenario's zichtbaar wordt. We geven ze hier mee zodat je ze kan herkennen als je ze tegenkomt.

Oefeningen

Richtlijnen

Structuur oefeningen

Vanaf hier veronderstellen we dat je in één groot project werkt dat één klasse Program heeft. Deze klasse heeft een Main methode die een keuzemenu opstart. Oefeningen rond eenzelfde topic worden (statische) methodes van één klasse met een methode ToonSubmenu, die je een menu toont van alle oefeningen over dat topic en die je toestaat een oefening naar keuze te testen. Dit wordt uitgelegd in de eerste oefening.

Oefening: H10-voorbereiding

Leerdoelen

  • een ordelijke menustructuur voor je code voorzien

Functionele analyse

We willen dat we alle oefeningen die we in dit vak maken op een ordelijke manier kunnen opstarten. We doen dit door een keuzemenu met twee niveaus te voorzien: de gebruiker kan elke reeds geschreven oefening uitvoeren door eerst het algemene onderwerp aan te geven en vervolgens de specifieke oefening.

Technische analyse

  • Laat in je Main methode een lijst van alle topics zien waarover oefeningen gemaakt zijn. In het begin is dit enkel DateTime. De gebruiker kan een topic aanduiden door een nummer in te geven, want voor elk topic staat ook een nummer.

  • Gebruik een switch op de gebruikersinput om te bepalen van welk topic de ToonSubmenu methode moet worden opgeroepen. Deze methode heeft return type void en geen parameters.

  • Voorzie een eerste klasse, DateTimeOefeningen, met deze methode ToonSubmenu. Totdat je oefeningen hebt om te demonstreren, toont ToonSubmenu gewoonweg de tekst "Er zijn nog geen oefeningen over dit topic".

  • Indien er wel oefeningen zijn (deze oefening moet je dus updaten naarmate je vordert), wordt elke reeds geprogrammeerde oefening genummerd en getoond en kan de gebruiker kiezen om deze uit te voeren.

  • Nadat een oefening getest is, kan je opnieuw een topic en een oefening kiezen. Het programma eindigt nooit.

Voorbeeldinteractie

Dit is maar een voorbeeld! De getoonde topics en oefeningen gaan afhangen van wat je al gedaan hebt.

Oefening: H10-dag-van-de-week

Leerdoelen

  • aanmaken van DateTime objecten

  • formatteren van DateTime objecten

Functionele analyse

We willen voor een willekeurige datum kunnen bepalen welke dag van de week het is.

Technische analyse

  • je moet eerst de dag, maand en jaar opvragen en een DateTime aanmaken

  • daarna moet je laten zien over welke dag van de week het gaat

    • gebruik hiervoor formattering van een DateTime

    • laat ook de datum zelf zien in een formaat dat leesbaar is voor de gebruiker

    • als je computer niet volledig ingesteld is op Belgisch Nederlands, kan het resultaat er wat anders uitzien.

  • maak deze methode toegankelijk via ToonSubmenu van de klasse DateTimeOefeningen

  • noem de methode waarin je dit schrijft WeekdagProgramma

Voorbeeldinteractie

Oefening: H10-ticks-sinds-2000

Leerdoelen

  • aanmaken van DateTime objecten

Functionele analyse

We willen weten hoe veel fracties van een seconde al verlopen zijn sinds het begin van de jaren 2000.

Technische analyse

  • .NET stelt deze fracties (1 / 10000 milliseconden) voor als "ticks"

  • We willen weten hoe veel ticks er voorbijgegaan zijn sinds het absolute begin van het jaar 2000

  • maak deze methode toegankelijk via ToonSubmenu van de klasse DateTimeOefeningen

  • Noem de methode waarin je dit schrijft Ticks2000Programma

Voorbeeldinteractie

Oefening: H10-schrikkelteller

Leerdoelen

  • gebruik van een statische methode

Functionele analyse

We willen bepalen hoe veel schrikkeljaren er zijn tussen 1799 en 2021.

Technische analyse

  • implementeer zelf geen logica voor schrikkeljaren, maar laat dit over aan de klassen DateTime

  • maak gebruik van een statische methode van deze klasse

  • maak deze methode toegankelijk via ToonSubmenu van de klasse DateTimeOefeningen

  • noem je methode SchrikkeljaarProgramma

Voorbeeldinteractie

Oefening: H10-simpele-timing

Leerdoelen

  • eenvoudig code leren timen

  • gebruiken van DateTime

  • herhaling arrays

Functionele analyse

We zijn benieuwd hoe lang het duurt een array van 1 miljoen ints te maken en op te vullen met de waarden 1,2,...

Technische analyse

  • Bepaal het tijdstip voor en na aanmaken van de array.

  • Vul de array in met een for-lus.

  • maak deze methode toegankelijk via ToonSubmenu van de klasse DateTimeOefeningen

  • Noem de methode waarin je dit schrijft ArrayTimerProgramma

Voorbeeldinteractie

Oefening: H10-verjaardag-v2

Leerdoelen

  • leren werken met objecten

  • gebruik maken van properties en methodes

Functionele analyse

We zullen het programma uit om het aantal dagen tot een verjaardag te bepalen (uit de theorie) aanpassen zodat het aantal dagen tot de volgende verjaardag wordt getoond. Dit betekent dat er nooit 0 dagen tot een verjaardag zijn, maar in extreme gevallen duizenden dagen kunnen over gaan (bijvoorbeeld: van 29 februari 1996 tot 29 februari 2004).

Technische analyse

Je moet één geval toevoegen, namelijk het geval waarin het oude programma 0 dagen tot de volgende verjaardag zou geven.

Noem je methode VerjaardagProgramma. Maak deze methode toegankelijk via ToonSubmenu van de klasse DateTimeOefeningen

Oefening: H10-Getallencombinatie

Leerdoelen

  • werken met klassen en objecten

  • instantieattributen

  • instantiemethoden

Functionele analyse

Dit programma geeft op basis van de input van twee getallen de som van beide getallen, het verschil, het product en de deling. In het laatste geval en indien er een deling door nul zou worden uitgevoerd, wordt dit woordelijk weergegeven.

Technische analyse

Voorzie voor volgende oefening eerst een nieuwe submenuklasse met als naam EigenObjectOefeningen.

Maak een eigen klasse GetallenCombinatie in een eigen file, GetallenCombinatie.cs. Deze klasse bevat 2 getallen (type int). Er zijn 4 methoden, die allemaal een double teruggeven:

  • Som: geeft som van beide getallen weer

  • Verschil: geeft verschil van beide getallen weer

  • Product: geeft product van beide getallen weer

  • Quotient: geeft deling van beide getallen weer. Print "Fout" naar de console indien je zou moeten delen door 0 en voer dan de deling uit. Wat er dan gebeurt, is niet belangrijk.

Gebruik public attributen Getal1 en Getal2. Plaats onderstaande code in een publieke statische methode van EigenObjectOefeningen met naam DemonstreerOperaties met return type void:

Zorg dat je DemonstreerOperaties kan oproepen via het submenu van EigenObjectOefeningen.

Voorbeeldinteractie(s)

Oefening: H10-StudentKlasse

Deze oefening veronderstelt dat je de theoriefilmpjes hebt gevolgd en dat je daar de klasse Student al hebt aangemaakt in een SchoolAdmin project.

Leerdoelen

  • werken met klassen en objecten

  • opstart van het project

Functionele analyse

Dit programma vraagt om de naam en leeftijd van een student. Vervolgens worden de punten voor 3 vakken gevraagd, waarna het gemiddelde wordt teruggegeven.

Technische analyse

Breid je klasse Student uit met een tweede array CursusResultaten. Voorzie ook een methode Kwoteer om een cijfer aan een cursus met een bepaalde index toe te kennen. Signaleer ongeldige waarden met een Console.WriteLine("Ongeldig cijfer!"). Je kan ook nooit een cijfer boven 20 behalen.

Voeg aan de klasse een methode Gemiddelde() toe. Deze berekent het gemiddelde van de niet-null cursussen als double.

Maak het return type van BepaalWerkbelasting ook een byte.

Voeg ook een methode ToonOverzicht toe, die de persoonsgegevens en behaalde cijfers voor elke student toont. Kijk naar de voorbeeldinteractie voor de juist voorstellingswijze.

Test je programma door een statische methode (in de klasse Student), DemonstreerStudenten te voorzien, die twee studenten aanmaakt via variabelen student1, student2. Elke student is ingeschreven voor minstens drie vakken die je zelf kiest en krijgt een geldig cijfer (naar keuze) voor elk vak, een naam en een geldige leeftijd. Vervolgens wordt van elke student de ToonOverzicht-methode opgeroepen. In je Main-methode voorzie je een (niet-genest) keuzemenu dat vraagt wat je wil doen en op dit moment is de enige optie DemonstreerStudenten uitvoeren.

Een overzicht van de klasse na al deze wijzigingen:

Voorbeeldcode om de eerste student aan te maken:

Voorbeeldinteractie(s)

Commit je aanpassingen na deze oefening!

Oefening: H10-Cursus

Deze klasse hoort bij het SchoolAdmin project.

Leerdoelen

  • werken met klassen en objecten

  • opstart van het project

  • arrays van objecten maken

Functionele analyse

We zullen studenten groeperen in cursussen. Bij elke cursus horen op dit moment exact twee studenten.

Technische analyse

Werk verder in het SchoolAdmin project. Maak in dit nieuw project een nieuwe klasse Cursus in een file Cursus.cs. Deze klasse heeft twee attributen: Studenten en Titel. Studenten is een array van Student-objecten. De initiële waarde voor dit attribuut is een array met een capaciteit van 2 studenten. Titel is gewoonweg een string. Cursus heeft ook een methode ToonOverzicht die de titel van de cursus toont, gevolgd door de namen van alle studenten die de cursus volgen.

Test je programma door een statische methode (in de klasse Cursus), DemonstreerCursussen te voorzien, die vier cursussen ("Communicatie", "Databanken", "Programmeren" en "Webtechnologie") aanmaakt via variabelen communicatie, programmeren, webtechnologie en databanken. Maak ook twee studenten aan (dezelfde als in DemonstreerStudenten) en maak hen lid van de cursussen waarvoor ze een cijfer hebben (zie voorbeeldinteractie van de vorige oefening). Toon tenslotte voor elke cursus het overzicht via ToonOverzicht. De methode DemonstreerCursussen kan ook opgeroepen worden via het keuzemenu in Main.

Je klasse Cursus ziet er uiteindelijk zo uit:

Voorbeeldinteractie(s)

Commit je aanpassingen!

Oefening: H10-CursusResultaat

Leerdoelen

  • wegwerken gesynchroniseerde arrays

  • encapsulatie

  • access modifiers

Functionele analyse

De eerdere oefening H10-StudentKlasse gebruikte gesynchroniseerde arrays. We willen deze wegwerken.

Technische analyse

Voorzie een klasse CursusResultaat met twee velden: Naam en Resultaat. Het eerste stelt de naam van de cursus voor, het tweede het behaalde resultaat.

Vervang vervolgens de arrays cursussen en CursusResultaten door één private array van objecten van deze nieuwe klasse met naam cursusResultaten. Vervang RegistreerVoorCursus door RegistreerCursusResultaat om dit mogelijk te maken (met een parameter voor de naam en een parameter voor het cijfer). DemonstreerStudenten moet identiek dezelfde uitvoer blijven produceren als tevoren.

Deze oefening vraagt om veel aanpassingen, maar ze zijn niet zo groot. Hou vooral je hoofd erbij en denk goed na over hoe je elk fout gemarkeerd stukje code kan herschrijven wanneer je de oude arrays hebt verwijderd.

Commit je aanpassingen!

Oefening: H10-Figuren

Leerdoelen

  • werken met klassen en objecten

  • gebruik maken van properties om geldige waarden af te dwingen

Functionele analyse

Dit programma maakt enkele rechthoeken en driehoeken met gegeven afmetingen (in meter) aan, berekent hun oppervlakte en toont deze info aan de gebruiker. De rechthoeken en driehoeken die worden aangemaakt, zijn al gecodeerd in het programma. De gebruiker hoeft dus niets anders te doen dan het programma te starten.

Technische analyse

Er is een klasse Rechthoek met full properties Breedte en Hoogte en een klasse Driehoek met Basis en Hoogte. Je programma maakt de figuren die hierboven beschreven worden aan met beginwaarde 1.0 voor elke afmeting en stelt daarna hun afmetingen in via de setters voor deze properties. De oppervlakte wordt bepaald in een read-only property (dus met alleen een getter en geen setter). Deze heet Oppervlakte en is van het type double.

Indien om het even welk van deze properties wordt ingesteld op 0 of minder, signaleer je dit via de code Console.WriteLine($"Het is verboden een (afmeting) van (waarde) in te stellen!") (zie voorbeeldcode).

De wiskundige formule voor de oppervlakte van een driehoek is basis * hoogte / 2.

Schrijf de voorbeelden uit in een static methode DemonstreerFiguren van de klasse EigenObjectOefeningen.

Voorbeeldinteractie(s)

(Er worden twee rechthoeken en twee driehoeken aangemaakt. De afmetingen van de eerste rechthoek worden eerst op -1 en 0 ingesteld. Daarna krijgen ze de waarden die je ziet in het bericht hieronder. Formatteer ook met 1 cijfer na de komma.)

De letter f is symbool nummer 102. Dit is omdat het "Unicode number" hexadecimaal wordt uitgedrukt. De HTML-code is decimaal.
Het symbool voor recyclage is symbool nummer 9851. Zelfde uitleg als voor de letter f.
eendimensionale array, getekend van links naar rechts
eendimensionale array, getekend van boven naar onder
eendimensionale voorstelling OXO
tweedimensionale OXO

Surf naar de webstek: . Hier wordt het "Game of Life" uitgelegd. We gaan een vereenvoudigde console versie maken. Dit wordt bijvoorbeeld veel gebruikt om automatisch omgevingen met structuur te genereren in games met procedurele generatie.

Klasse toevoegen in VS
basics oop same in vv

De eerdere oefening kan wat mooier gepresenteerd worden. Ze zou ook ingezet moeten kunnen worden in regio's waar de euro niet gebruikt wordt.

Via de constructor kunnen we beginwaarden meegeven bij het maken van een nieuw object. Er zijn 11 manieren waarop dit kan zoals je .

Een lijst van alle cultures in .NET kan je .

(let op: de demonstratie SchoolAdmin is verouderd en wordt anders aangepakt in 2022. De tekst heeft voorrang!)

Een veld krijgt normaal de defaultwaarde voor zijn type. hebben we reeds gezien. Het is mogelijk de beginwaarde aan te passen met de syntax voor een toekenning:

Standaard is hij public ()

kunnen we ook constructors overloaden, d.w.z. alternatieven met dezelfde naam voorzien maar met een ander stel parameters. De code is gelijkaardig als bij method overloading:

De richtlijnen rond naamgeving van Microsoft met betrekking tot attributen, methoden,... vind je terug.

. De camerabeelden zijn wat wazig, maar de schermopname is in orde.

https://www.compadre.org/osp/EJSS/3577/12.htm
Kennisclip
Kennisclip. Klein verschil met de pagina tot de twee oranje checkboxes, dus lees die goed.
hier kan zien
Dit wordt hier volledig uit de doeken gedaan.
hier terugvinden
Kerstinkopen
class Auto {

    // objectvariabelen van eerder zijn er nog

    public void Voltanken()
    {
        Benzine = 50.0; // we veronderstellen even dat dat het maximum is
    }

    public void Rijden(int aantalKilometers)
    {
        Kilometers += aantalKilometers;
        Benzine -= 5.0 * (aantalKilometers/100.0);
    }

    public void Onderhouden()
    {
        LaatsteOnderhoud = DateTime.Now;
    }

    public double VerkoopsprijsBepalen()
    {
        return Math.Max(10000 * (1 - Kilometers / 200000.0),1000);
    }
}
// in Program.cs
public static void DemonstreerAttributen() {
    Auto auto1 = new Auto();
    Auto auto2 = new Auto();
    auto1.Voltanken();
    auto1.Rijden(5);
    auto1.Rijden(10);
    auto1.Rijden(20);
    Console.WriteLine(auto1.Kilometers);
    Console.WriteLine(auto2.Kilometers);
}
enum MilieuNormen {
    Euro1, Euro2, Euro3
}

class Auto {
    public static MilieuNormen HuidigeNorm;
    // rest van de code voor Auto
    public static void VerklaarNorm(MilieuNormen norm) {
        int jaartal;
        double CO;
        switch (norm) {
            case MilieuNormen.Euro1:
                jaartal = 1992;
                CO = 1.0;
                break;
            case MilieuNormen.Euro2:
                jaartal = 1996;
                CO = 1.0;
                break;
            case MilieuNormen.Euro3:
                jaartal = 2000;
                CO = 0.64;
                break;
            default:
                jaartal = -1;
                CO = -1;
                break;
        }
        Console.WriteLine($"Geïntroduceerd in {jaartal}");
        Console.WriteLine($"{CO} gram CO per km");
    }
}
class Program {
    public static void Main() {
        int aantalAutos = 3;
        int[] kilometers = new int[aantalAutos];
        double[] benzine = new double[aantalAutos];
        DateTime[] onderhoud = new DateTime[aantalAutos];
        for (int i = 0; i < aantalAutos; i++) {
            Console.WriteLine($"Kilometerstand van auto {i+1}?");
            kilometers[i] = Convert.ToInt32(Console.ReadLine());
            Console.WriteLine($"Benzinepeil van auto {i+1}?");
            benzine[i] = Convert.ToDouble(Console.ReadLine());
            Console.WriteLine($"Jaar recentste onderhoud auto {i+1}?");
            int jaar = Convert.ToInt32(Console.ReadLine());
            Console.WriteLine($"Maand recentste onderhoud auto {i+1}?");
            int maand = Convert.ToInt32(Console.ReadLine());
            Console.WriteLine($"Dag recentste onderhoud auto {i+1}?");
            int dag = Convert.ToInt32(Console.ReadLine());
            onderhoud[i] = new DateTime(jaar,maand,dag);
        }
        // later in de code
        for (int i = 0; i < aantalAutos; i++) {
            PrintOnderhoudsrapport(kilometers[i],benzine[i],onderhoud[i]);
        }
    }
}
class Auto {
    public int Kilometers;
    public double Benzine;
    public DateTime LaatsteOnderhoud;
}
class Program {
    public static void Main() {
        int aantalAutos = 3;
        Auto[] autos = new Auto[aantalAutos];
        for (int i = 0; i < aantalAutos; i++) {
            Auto nieuweAuto = new Auto();
            autos[i] = nieuweAuto;
            Console.WriteLine($"Kilometerstand van auto {i+1}?");
            nieuweAuto.Kilometers = Convert.ToInt32(Console.ReadLine());
            Console.WriteLine($"Benzinepeil van auto {i+1}?");
            nieuweAuto.Benzine = Convert.ToDouble(Console.ReadLine());
            Console.WriteLine($"Jaar recentste onderhoud auto {i+1}?");
            int jaar = Convert.ToInt32(Console.ReadLine());
            Console.WriteLine($"Maand recentste onderhoud auto {i+1}?");
            int maand = Convert.ToInt32(Console.ReadLine());
            Console.WriteLine($"Dag recentste onderhoud auto {i+1}?");
            int dag = Convert.ToInt32(Console.ReadLine());
            nieuweAuto.LaatsteOnderhoud = new DateTime(jaar,maand,dag);
        }
        // later in de code
        for (int i = 0; i < aantalAutos; i++) {
            PrintOnderhoudsrapport(autos[i]);
        }
    }
}
class Auto {
    public int Kilometers = 5; // in de fabriek vinden bv. een aantal testen plaats
    public double Benzine = 10; // nieuwe auto's moeten kunnen rijden
    public DateTime LaatsteOnderhoud = DateTime.Now;
}
enum MilieuNormen {
    Euro1, Euro2, Euro3, Euro4, Euro5, Euro6
}

class Auto {
    public static MilieuNormen HuidigeNorm;
    // rest van de code voor Auto
}
Auto auto1 = new Auto();
class Auto
{
    private int benzine = 5;
    private int kilometers;
}
class Auto
{
    private int benzine = 5;
    private int kilometers;

    public Auto()
    {
        // zet hier de code die bij initialisatie moet gebeuren
        // d.w.z. wat er moet gebeuren zodra de auto wordt gemaakt
    }
}
class Auto
{
    private int benzine = 5;
    private int kilometers;
    private static Random randomGen = new Random();

    public Auto()
    {
        // ook in de constructor kan je this gebruiken
        this.kilometers = randomGen.Next(0,200000);
    }
}
Auto auto1= new Auto(25,20000);
class Auto
{
    int benzine;
    int kilometers;

    public Auto(int benzine, int kilometers)
    {
        this.benzine = benzine;
        this.kilometers = kilometers;
    }
}
class Auto
{
    int benzine;
    int kilometers;

    public Auto(int benzine, int kilometers)
    {
        this.benzine = benzine;
        this.kilometers = kilometers;
    }

    public Auto() {
    }
}
class Auto
{
    private int benzine;
    private int kilometers;

    public Auto(int benzine, int kilometers)
    {
        this.benzine = benzine;
        this.kilometers = kilometers;
    }

    public Auto() : this(5,5)
    {
        // hier gebeurt niets meer
        // maar : this(5,5) zorgt dat een oproep van deze constructor
        // eerst de andere oproept, met beide waarden 5
    }
}
public class Auto
{
    private int kilometers;
    private double benzine;
}
Auto auto = new Auto();
auto.benzine += 10; //DIT ZAL DUS NIET WERKEN, daar benzine private is.
public class Auto
{
    private int kilometers;
    private double benzine;
    public double GetBenzine() {
        return this.benzine;
    }
    public void SetBenzine(double waarde) {
        if(waarde >= 0 && waarde <= 50) {
            this.benzine = waarde;
        }
    }
}
Auto auto = new Auto();
auto.Benzine += 10;
Auto auto = new Auto();
auto.SetBenzine(auto.GetBenzine() + 10);
class Auto
{
    private int kilometers;
    private double benzine;

    public double Benzine
    {
        get
        {
            return benzine;
        }
        set
        {
            benzine = value;
        }
    }
}
Auto auto = new Auto();
auto.Benzine = 20; //set
Console.WriteLine($"Het benzinepeil is {auto.Benzine}"); //get
   public double Benzine
    {
        get
        {
            return benzine;
        }
        set
        {
            if(value >= 0 and value <= 50) {
                benzine = value;
            }
        }
    }
   public double Benzine
    {
        set
        {
            if(value >= 0) {
                benzine = value;
            }
        }
    }
   public double Benzine
    {
        get
        {
            return benzine;
        }
    }
   public double Benzine
    {
        get
        {
            return benzine;
        }
        private set
        {
            if(value >= 0) {
                benzine = value;
            }
        }
    }
public class Auto
{
    private int kilometers;
    private double benzine;
    // stelt het aantal blokjes benzine voor op je display
    // bij 50l heb je 5 blokjes
    // bij tussen 40 en 50l heb je 4 blokjes
    // ...
    // bij minder dan 10l heb je 0 blokjes
    public int Blokjes {
        get {
            return Math.Floor(this.benzine / 10);
        }
    }
}
public class Auto
{
    public double Benzine
    { get; set; }
}
Welkom bij de demo Objectgeoriënteerd Programmeren!
Topic van de uit te voeren oefening?
1. DateTime
2. Properties en access modifiers
> 1
Uit te voeren oefening?
1. H10-dag-van-de-week
2. H10-ticks-sinds-2000
3. H10-schrikkelteller
> 2
Sinds 1 januari 2000 zijn er (...) ticks voorbijgegaan.
Topic van de uit te voeren oefening?
(...)
Welke dag?
> 14
Welke maand?
> 2
Welk jaar?
> 2020
14 februari 2020 is een vrijdag.
Sinds 1 januari 2000 zijn er (hier wordt het aantal getoond) ticks voorbijgegaan.
Er zijn (hier wordt het aantal getoond) schrikkeljaren tussen 1799 en 2021.
Het duurt (hier wordt het aantal getoond) milliseconden om een array van een miljoen elementen aan te maken en op te vullen met opeenvolgende waarden.
GetallenCombinatie paar1 = new GetallenCombinatie();
paar1.Getal1 = 12;
paar1.Getal2 = 34;
Console.WriteLine("Paar:" + paar1.Getal1 + ", " + paar1.Getal2);
Console.WriteLine("Som = " + paar1.Som());
Console.WriteLine("Verschil = " + paar1.Verschil());
Console.WriteLine("Product = " + paar1.Product());
Console.WriteLine("Quotient = " + paar1.Quotient());
Paar: 12, 34
Som = 46
Verschil = -22
Product = 408
Quotient = 0,352941176470588
Student student1= new Student();
student1.Geboortedatum = new DateTime(2001,1,3);
student1.Naam = "Said Aziz";
student1.RegistreerVoorCursus("Communicatie");
student1.CursusResultaten[0] = 12;
student1.RegistreerVoorCursus("Programmeren");
student1.CursusResultaten[1] = 15;
student1.RegistreerVoorCursus("Webtechnologie");
student1.CursusResultaten[2] = 13;
student1.ToonOverzicht();
Wat wil je doen?
1. DemonstreerStudenten uitvoeren
> 1
Said Aziz, 20 jaar

Cijferrapport:
**********
Communicatie:             12
Programmeren:             15
Webtechnologie:           13
Gemiddelde:               13.3

Mieke Vermeulen, 21 jaar

Cijferrapport:
**********
Communicatie:             13
Programmeren:             16
Databanken:               14
Gemiddelde:               14.3
Wat wil je doen?
1. DemonstreerStudenten uitvoeren
2. DemonstreerCursussen uitvoeren
> 2
Communicatie
Said Aziz
Mieke Vermeulen

Databanken
Mieke Vermeulen

Programmeren
Said Aziz
Mieke Vermeulen

Webtechnologie
Said Aziz
Het is verboden een breedte van -1 in te stellen!
Het is verboden een breedte van 0 in te stellen!
Een rechthoek met een breedte van 2,2m en een hoogte van 1,5m heeft een oppervlakte van 3,3m².
Een rechthoek met een breedte van 3m en een hoogte van 1m heeft een oppervlakte van 3m².
Een driehoek met een basis van 3m en een hoogte van 1m heeft een oppervlakte van 1,5m².
Een driehoek met een basis van 2m en een hoogte van 2m heeft een oppervlakte van 2m².
Defaultwaarden

Oefeningen

Oefening: H11-Figuren

Leerdoelen

  • werken met klassen en objecten

  • gebruik maken van properties om geldige waarden af te dwingen

  • gebruik van een constructor

Functionele analyse

Functioneel is dit programma hetzelfde als H10-figuren.

Technische analyse

Voorzie je eerdere figuren (Rechthoek en Driehoek) van een constructor met twee parameters, waarvan de tweede telkens de hoogte voorstelt en de eerste de andere afmeting. Zorg dat deze constructor gebruik maakt van de properties, zodat je makkelijker objecten met de juiste afmetingen kan maken en toch dezelfde foutmeldingen krijgt als eerder.

Je zal een extra, parameterloze, constructor moeten toevoegen omdat DemonstreerFiguren van eerder moet blijven werken. Schrijf een nieuwe methode DemonstreerFigurenMetConstructor om te tonen dat je hetzelfde kan bereiken met de constructor met twee parameters. Deze moet ook opgeroepen kunnen worden van uit je keuzemenu.

Voorbeeldinteractie(s)

Schrijf DemonstreerFigurenMetConstructor zodanig dat je exact onderstaande interactie krijgt:

Het is verboden een breedte van -1 in te stellen!
Het is verboden een hoogte van 0 in te stellen!
Een rechthoek met een breedte van 2,2m en een hoogte van 1,5m heeft een oppervlakte van 3,3m².
Een rechthoek met een breedte van 3m en een hoogte van 1m heeft een oppervlakte van 3,0m².
Een driehoek met een basis van 3m en een hoogte van 1m heeft een oppervlakte van 1,5m².
Een driehoek met een basis van 2m en een hoogte van 2m heeft een oppervlakte van 2,0m².

Uitbreiding CursusResultaat (SchoolAdmin project)

Leerdoelen

  • makkelijker objecten aanmaken

  • gebruik maken van properties om geldige waarden af te dwingen

Functionele analyse

Functioneel zal je niet veel verschil zien met eerder. Dit is zuiver een aanpassing die de kwaliteit van je code verhoogt.

Technische analyse

Pas CursusResultaat aan zodat het huidige attribuut Naam privaat wordt (hernoem het dan ook naar naam) en voeg een read-only property Naam toe om deze informatie toch toegankelijk te houden.

Voor Resultaat doe je een gelijkaardige aanpassing, maar de property is niet read-only. Hij kan ingesteld worden, maar enkel op een waarde tussen 0 en 20. Aanpassingen naar een andere waarde worden genegeerd.

Voeg ook een constructor toe met twee parameters. De eerste is voor de naam, de tweede voor het resultaat. Doe ten slotte nodige wijzigingen in andere onderdelen van je code om met deze nieuwe code te werken, zonder iets te veranderen aan het gedrag van je systeem.

Uitbreiding Cursus (SchoolAdmin project)

Leerdoelen

  • informatie op klasseniveau bijhouden

  • meer toepassingen van de constructor

Functionele analyse

We wensen cursussen automatisch te nummeren (zoals in DigitAP ook elke cursus een nummer heeft).

Technische analyse

Voorzie eerst de klasse Cursus van een read-only property Id van type int. Pas ook de klasse Cursus aan zodanig dat het volgende beschikbare nummer voor een cursus wordt bijgehouden in een variabele maxId. De eerste cursus zal nummer 1 moeten krijgen. Zorg er ten slotte voor dat elke nieuwe cursus automatisch dit eerste beschikbare nummer krijgt en dat nummer stijgt voor elke cursus die wordt aangemaakt.

Neem dit nummer ook op in de methode ToonOverzicht van cursus, zodanig dat het cursusnummer tussen haakjes verschijnt, naast de titel van de cursus.

Verdere uitbreiding Cursus (SchoolAdmin project)

Leerdoelen

  • properties en access control

  • meer toepassingen van de constructor

Functionele analyse

We willen het aantal studiepunten per cursus bijhouden. We willen niet dat het veel extra werk is om dit aantal in te geven en we weten dat de meeste cursussen 3 studiepunten tellen.

Technische analyse

Voorzie eerst de klasse Cursus van een property Studiepunten van type byte. Je hoeft hierbij geen speciale controles te doen en mag gewoon het algemene patroon volgen, maar maak de setter private.

Pas de constructor aan, zodat hij ook het aantal studiepunten aanvaardt als (derde) parameter. Zorg er met behulp van chaining ook voor dat ook calls met een of twee argumenten geldig blijven, waarbij het aantal studiepunten van vanzelf wordt ingesteld op 3.

Zet ook het aantal studiepunten mee in het overzicht dat je krijgt via ToonOverzicht. Zet tussen haakjes naast het nummer voor de cursus, gevolgd door "stp".

Pas ten slotte, in DemonstreerCursussen, je code aan zodat het vak webtechnologie wordt aangemaakt met ruimte voor 5 studenten en 6 studiepunten telt, terwijl databanken ruimte krijgt voor 7 studenten en 5 studiepunten telt.

Student uit tekst lezen

Leerdoelen

  • werken met strings

  • werken met arrays

Functionele analyse

Voor de administratie is het handig snel en efficiënt nieuwe studenten te kunnen registreren. Zorg ervoor dat een gebruiker één regel tekst kan intypen met alle gegevens over een student, zonder veel verdere interactie.

Technische analyse

Schrijf een methode StudentUitTekstFormaat(string csvWaarde) die een object van de klasse Student teruggeeft. Deze methode mag veronderstellen dat csvWaarde eerst de naam van de student bevat, gevolgd door een puntkomma, gevolgd door de geboortedag, puntkomma, geboortemaand, puntkomma, geboortejaar. Alle elementen van de geboortedatum worden voorgesteld als getallen, volgens de afspraken die je ook toepast om datums te noteren in het Belgische formaat. Het kan zijn dat er ook informatie is om de student meteen te registreren voor een of meerdere cursusresultaten. In dat geval staat er na het geboortejaar nog een puntkomma, dan de naam van de cursus, dan het behaalde cijfer. Per cursus herhaalt deze groep van twee elementen zich.

Schrijf daarna een methode DemonstreerStudentUitTekstFormaat(). Deze vraagt om de tekstvoorstelling van één student in te typen, maakt de student aan en toont dan het overzicht voor deze student. Neem deze methode ook op als optie in je keuzemenu voor SchoolAdmin.

Deze methode vereist geen bestaande Student. Ze heeft wel te maken met de klasse Student.

De student hoeft niet opgenomen te worden in de array Studenten van een Cursus-object. We verbeteren dit later nog.

Voorbeeldinteractie

Geef de tekstvoorstelling van 1 student in CSV-formaat:
>Bart Van Steen;04;03;1998;Boekhouden;14;Macro-economie;8;Frans, deel 2;18

Bart Van Steen, 22 jaar

Cijferrapport:
**********
Boekhouden:     14
Macro-economie: 8
Frans, deel 2:  18
Gemiddelde      13,3

value en reference met eigen objecten

Value types vs reference types

Twee soorten datatypes

Je gegevens in een C#-programma zijn altijd van een bepaald type: string, int, DateTime, Student, wat dan ook. Je moet voortdurend nadenken over welke datatype je aan het gebruiken bent. Dit is niet alleen belangrijk om te weten welke methoden en attributen je mag gebruiken. Je moet het ook weten om te begrijpen wat gebeurt als je een waarde toekent of als je een waarde meegeeft als argument van een methode.

Er zijn namelijk twee mogelijkheden:

  • bij value types overschrijf je de data zelf wanneer je een toekenning doet en geef je een kopie van je data mee aan de methode

    • Deze types kunnen standaard niet de waarde null aannemen. Als je een variabele van een van deze types maakt en niet toekent, krijgt hij een defaultwaarde, zoals 0, false,...

  • bij reference types noteer je een geheugenadres wanneer je een toekenning doet en geef je een geheugenadres mee wanneer je een methode oproept

    • deze types kunnen (standaard) de waarde null aannemen, wat betekent dat er geen adres voor de data is. Dit is ook de defaultwaarde voor alle reference types.

Dit heeft belangrijke gevolgen. Als je dit systeem niet begrijpt, ga je gegarandeerd bugs in je code zien. Als voorbeeld zullen we het uitvoeren van een methode vergelijken met "iets noteren op papier" in het echte leven. Afhankelijk van de situatie wil je dan met een kopie of met een adres voor het origineel werken.

  • Op een toets krijgt iedereen een blad met de vragen en vult hij/zij persoonlijke antwoorden in. Elke toets is individueel. Het is niet zo dat het antwoord van persoon 1 zichtbaar mag zijn voor persoon 2. We nemen dan ook geen toetsen af in Google docs.

  • Wanneer er een geboortekaartje moet ondertekend worden, wordt er op de werkvloer vaak een e-mail uitgestuurd waarin staat in welk lokaal het kaartje ligt. Alle aanpassingen komen samen op hetzelfde kaartje.

We vragen niet aan iedereen om dezelfde toets in te vullen en we voorzien geen geboortekaartje per persoon die ondertekent. Je ziet dus andere resultaten wanneer je een handeling telkens uitvoert met een kopie dan wanneer je ze uitvoert met een verwijzing naar het origineel.

Klassen zijn reference types

Onze eigen klassen zijn reference types. Dat wil zeggen dat, in de ruimte die voorzien wordt wanneer we een variabele van een bepaalde klasse declareren, er een verwijzing wordt bijgehouden. Zo'n verwijzing is een adres voor de bytes die ons object vormen. Dit is in tegenstelling tot value types. Daarvoor wordt de waarde zelf bijgehouden op de plaats die voorzien is voor de variabele. De meeste types die je in het begin gezien hebt, zijn value types: int (en varianten), boolean, float (en varianten), enum types.

Demonstratie: leeftijd als onderdeel van een klasse en als losse variabele

public class Student {
    public int Leeftijd = 18;
}

public class Program {
    public static void VerhoogLeeftijd(int leeftijd) {
        leeftijd += 1;
    }
    public static void VerhoogLeeftijd(Student student) {
        student.Leeftijd += 1;
    }
    public static void Main() {
        int leeftijdAlsInt = 18;
        Student student = new Student();
        student.Leeftijd = 18;
        VerhoogLeeftijd(leeftijdAlsInt);
        VerhoogLeeftijd(student);
        Console.WriteLine(leeftijdAlsInt);
        Console.WriteLine(student.Leeftijd);
    }
}

Deze code produceert toont twee verschillende waarden: 18 en 19.

We verklaren de 18 als volgt:

  • leeftijdAlsInt is een int, dus een value type, waaraan we de waarde 18 geven

  • als we VerhoogLeeftijd met een int als parameter oproepen, maken we dus een kopie van 18 en geven we die kopie mee aan de methode. Het is alsof we de methode een eigen exemplaar van de toets geven. In de body van de methode is "leeftijd" dus een naam om de kopie aan te duiden.

  • als we leeftijd met 1 verhogen (via leeftijd += 1 ofwel leeftijd = leeftijd + 1) koppelen we de naam "leeftijd" aan een nieuwe waarde. Dat heeft geen enkel effect op leeftijdAlsInt, waarvan we bij oproep van VerhoogLeeftijd een kopie hadden genomen.

  • de Console.WriteLine toont de originele leeftijd en die is nooit aangepast

We verklaren de 19 als volgt:

  • Student is een zelf geschreven klasse, dus een reference type

  • als we student.Leeftijd instellen op 18, gaan we eerst op zoek naar de geheugenlocatie waar de gegevens over student zijn bijgehouden en overschrijven we daar de bytes die de leeftijd voorstellen.

  • als we VerhoogLeeftijd met student als parameter aanroepen, vertellen we de methode waar in het geheugen de informatie over student gevonden kan worden. Het is alsof we zeggen: "Het geboortekaartje (de student) ligt in de refter".

  • VerhoogLeeftijd gaat de leeftijd dus aanpassen bij de bron: de bytes die overschreven worden, zijn dezelfde die we de eerste keer hebben ingevuld.

  • de Console.WriteLine gaat naar het adres waar de bytes van het Student object zich bevinden en haalt daar de leeftijd op: deze zijn in de vorige stap aangepast.

Demonstratie: wat als klassen value types waren?

Het feit dat klassen reference types zijn, heeft praktische gevolgen. We zullen dit demonstreren door dezelfde functionaliteit te implementeren met een class en een struct. Je kan struct zien als bijna hetzelfde als class, in die zin dat je er ook objecten van kan maken, maar struct-objecten zijn value types.

We gaan je nergens in deze cursus vragen zelf een struct te maken. We gebruiken ze alleen omdat ze het verschil tussen value en reference duidelijker kunnen maken.

Vergelijk volgende twee vereenvoudigde varianten op DateTime:

struct MiniDatumValue
{
    public int Dag;
    public int Maand;
    public int Jaar;
}
    
class MiniDatumReference
{
    public int Dag;
    public int Maand;
    public int Jaar;
}

Beide stellen een datum voor en hebben dezelfde attributen. We voorzien ook een methode WijzigDatums, met twee parameters: één voor ons value type en één voor ons reference type. Ten slotte voorzien we een demonstratie in Main:

static void Main(string[] args) {
    MiniDatumValue d1 = new MiniDatumValue();
    d1.Dag = 6;
    d1.Maand = 3;
    d1.Jaar = 2016;
    MiniDatumValue d2 = new MiniDatumReference();
    d2.Dag = 6;
    d2.Maand = 3;
    d2.Jaar = 2016;
    Program.WijzigDatums(d1,d2);
    Console.WriteLine($"value na uitvoering: {d1.Dag}/{d1.Maand}/{d1.Jaar}");
    Console.WriteLine($"reference na uitvoering: {d2.Dag}/{d2.Maand}/{d2.Jaar}");
}

static void WijzigDatums(MiniDatumValue val, MiniDatumReference reference)
{
    val.Maand = 2;
    reference.Maand = 2;
}

Als je dit programma uitvoert, merk je dat de waarde van d1 niet gewijzigd is door de methode en d2 wel. Dat komt omdat val een kopie bevatte van de waarde van d1, terwijl reference naar dezelfde data verwees als d2.

Kennisclip
Kennisclip
Kennisclip voor deze inhoud
meer info
Net zoals bij overloading van methoden
Kennisclip
hier
Kennisclip
Kennisclip voor deze inhoud

nullable value types

Betekenis van null

Normaal gezien kom je null tegen wanneer je een variabele van een reference type hebt zonder verwijzing naar data. Het gevolg is dat null vaak betekent dat er een waarde zou kunnen staan, maar in de huidige situatie geen geldige waarde is.

Hoewel value types niet werken met verwijzingen, zou dezelfde interpretatie ook bij value types zinvol kunnen zijn: soms heb je gewoonweg geen geldige waarde. Daarom kent C# ook nullable value types. Dit zijn speciale value types die ook de waarde null kunnen aannemen (ook al kom je die laatste anders vooral tegen bij reference types).

Je noteert een nullable value type als een gewoon value type, gevolgd door een vraagteken. Indien je in code bijvoorbeeld een getalwaarde wil voorstellen als een variabele int mijnVariabele, maar de mogelijkheid bestaat dat er geen waarde is voor mijnVariabele, declareer je als volgt: int? mijnVariabele. Dit betekent: "mijnVariabele is een getal, maar kan ontbreken."

Dit heeft gevolgen. Je kan code die een value type verwacht niet zomaar gebruiken met een nullable versie van hetzelfde type. Anders gezegd: je mag mijnVariabele niet meegeven aan een methode die een gewone int verwacht. Je moet ofwel deze methode aanpassen zodat ze een int? verwacht, ofwel moet je mijnVariabele casten voor je hem meegeeft als argument. Let op: dit werkt alleen als mijnVariabele niet null is!

Operaties met null

Voor de nullable versies van de value types die je al kent, kan je gekende operaties (zoals +, -, ... voor getallen) blijven gebruiken, maar je moet opletten. Een berekening met null in levert je sowieso null op als resultaat. Een vergelijking (via <=, <, >, >=) met null levert je sowieso false op als resultaat.

Spelen met strings

Je kan objecten aanmaken door een omschrijving ervan in tekst om te zetten naar een constructoroproep. We noemen dit proces "deserialisatie". Een object omzetten naar een omschrijving noemen we "serialisatie". Deze omschrijving kan verschillende conventies volgen: XML, JSON, YAML, CSV,...

Objecten deserializeren met CSV

CSV wordt vaak gebruikt om objecten tussen programma's te verplaatsen. Een object inlezen vanaf een CSV bestand is een vorm van deserializeren.

// Speler voornaam, familienaam, geboortejaar
string[] lijnen = File.ReadAllLines(@"C:\spelers.csv");
Speler[] spelers = new Speler[lijnen.Length];
for (int i = 0; i < lijnen.Length; i++)
{
    string[] kolomwaarden = lijnen[i].Split(',');
    spelers[i] = new Speler(kolomwaarden[0],kolomwaarden[1],Convert.ToInt32(kolomwaarden[2]));
}

CSV wegschrijven

Je kan tekst uit een bestand lezen, maar uiteraard kan je ook naar een bestand wegschrijven. De 2 eenvoudigste manieren zijn:

  • File.WriteAllText: deze gebruik je als je 1 enkele string wilt wegschrijven

  • File.WriteAllLines: deze is de omgekeerde van ReadAllLines() en zal een array van strings wegschrijven.

Een voorbeeld:

string[] stringArray = new string[]
    {
        "cat",
        "dog",
        "arrow"
    };


File.WriteAllLines("file.txt", stringArray);

Opgelet met het schrijven naar bestanden: dit zal onherroepelijk het target bestand overschrijven. Gebruik if(File.Exists(pathtofile)) om te controleren of een bestand bestaat of niet. Eventueel kan je dan aan de gebruiker bevestiging vragen of je deze effectief wilt overschrijven.

Wil je CSV-bestand maken dan zal je eerst met String.Join of met stringinterpolatie een komma-separated lijst maken, bijvoorbeeld:

Speler[] spelers = {
    new Speler("Tim","Dams",1981),
    new Speler("Jos","Stoffels",1970),
    new Speler("Mounir","Hamdaoui",1984)
};

string[] lines = new string[spelers.Length];
for (int i = 0; i < lines.Length; i++)
{
    Speler speler = spelers[i];
    lines[i] = $"{speler.Voornaam},{speler.Familienaam},{speler.Geboortejaar}";
}

System.IO.File.WriteAllLines("spelers.csv", lines);

NullReference exception

Null en NullReferenceException

Zoals nu duidelijk is bevatten variabelen van een reference type steeds een referentie naar een object. Maar wat als we dit schrijven:

Student stud1;
// Visual Studio staat niet toe een programme met deze code uit te voeren
// maar je kan het via command line wel doen
stud1.Naam = "Test";

Dit zal een fout geven. stud1 bevat namelijk nog geen referentie. Maar wat dan wel?

Deze variabele bevat de waarde null . Dit is de defaultwaarde voor reference types. Met andere woorden, als je een reference type declareert en niet initialiseert, zal de waarde null zijn.

NullReferenceException

Een veel voorkomende foutboodschap tijdens de uitvoer van je applicatie is de zogenaamde NullReferenceException . Deze zal optreden wanneer je code een member (attribuut, methode of property) van null probeert op te vragen.

Laten we dit eens simuleren:

Student stud1 = null;
Console.WriteLine(stud1.Name);

Dit zal resulteren in volgende foutboodschap:

We moeten in dit voorbeeld expliciet = null plaatsen daar Visual Studio slim genoeg is om je te waarschuwen voor eenvoudige potentiële NullReference fouten en je code anders niet zal compileren.

NullReferenceException voorkomen

Je kan NullReferenceException voorkomen door na te gaan dat een object verschillend is van null vooraleer je een van de members van dit object probeert te gebruiken. Bijvoorbeeld, met een klasse Auto:

static void Main() {
    Auto auto1 = new Auto();
    Auto auto2 = null;
    Console.WriteLine($"{auto1.GeefKilometerstand()}km");
    if (!(auto2 is null)) {
        Console.WriteLine($"{auto2.GeefKilometerstand()}km");
    }
    else {
        Console.WriteLine($"auto2 heeft geen waarde, kilometerstand opvragen zou crashen");
    }
}

Deze code zal niet crashen. Als je de WriteLine uitvoert zonder if, zal het programma wel crashen met een NullReferenceException.

Waarom is null en niet == null? Die vraag leidt ons te ver. Meestal zal == null ook werken, maar == kan aangepast worden om anders te werken dan gewoonlijk. is is dus betrouwbaarder.

Labo

SchoolAdmin project: alle cursussen opvolgen

Functionele analyse

We willen een lijst bijhouden met alle objecten van de klasse Cursus. Zorg dat deze lijst automatisch wordt ingevuld.

Technische analyse

Voorzie op klasseniveau een array met plaats voor 10 Cursus objecten en noem hem AlleCursussen. Zorg ervoor dat een cursus bij aanmaak in de eerste vrije positie in deze array wordt geplaatst. Schrijf hiervoor een private hulpmethode registreerCursus(Cursus cursus). Deze kan je uitvoeren zonder bestaand Cursus object. Ze gaat op zoek naar de eerste vrije positie in de array en slaat cursus op op deze positie.

Je kan registreerCursus als volgt implementeren:

  • start met een variabele vrijePositie van type int? met waarde null

  • controleer één voor één de posities in de array

    • onthoud de eerste positie waarop je null tegenkomt

  • controleer nadat de array doorlopen is of er nog een vrije positie is

    • zo ja, sla de cursus daar op

    • zo nee, print "Er zijn geen vrije posities meer"

Commit je aanpassingen.

SchoolAdmin project: cursussen opzoeken op Id

Functionele analyse

We willen cursussen makkelijk kunnen opvragen via Id. Schrijf een methode ZoekCursusOpId die dit doet.

Technische analyse

Deze methode werkt op klasseniveau, want je hebt geen cursus nodig om een andere cursus te vinden. Ze heeft één parameter, id. Het return type is Cursus, maar het kan zijn dat je geen cursus met het gevraagde Id kan terugvinden.

De methode werkt door AlleCursussen element per element te doorlopen en de waarde van het attribuut Id van elk element te vergelijken met het meegegeven argument. Als de gevraagde cursus niet bestaat, mag je programma niet crashen, maar krijg je null terug.

Commit je aanpassingen.

SchoolAdmin project: gelinkte objecten

Functionele analyse

Het is niet handig dat onze klasse VakInschrijving een cursus voorstelt als string. Dat maakt dat we de functionaliteit van Cursus niet kunnen gebruiken. Pas daarom VakInschrijving aan zodat de klasse echt gelinkt is aan Cursus. Dit vereist aanpassingen op een aantal plaatsen.

Technische analyse

Voor de aanpassing heb je dit:

Erna heb je dit:

Controleer ook dat al je testmethodes nog dezelfde resultaten leveren als eerder.

Commit je aanpassingen.

SchoolAdmin project: Studieprogramma (stap 1)

We wensen cursussen te groeperen in studieprogramma's.

Functionele analyse

Schrijf een klasse StudieProgramma. Deze heeft een naam, bevat een aantal cursussen en kan getoond worden op het scherm.

Technische analyse

Implementeer als volgt:

Gebruik volgende code voor de demonstratiemethode:

Cursus communicatie = new Cursus("Communicatie");
Cursus programmeren = new Cursus("Programmeren");
Cursus databanken = new Cursus("Databanken", new Student[7], 5);
Cursus[] cursussen = { communicatie, programmeren, databanken };
StudieProgramma programmerenProgramma = new StudieProgramma("Programmeren");
StudieProgramma snbProgramma = new StudieProgramma("Systeem- en netwerkbeheer");
programmerenProgramma.cursussen = cursussen;
snbProgramma.cursussen = cursussen;
// later wordt Databanken geschrapt uit het programma SNB
snbProgramma.cursussen[2] = null;
programmerenProgramma.ToonOverzicht();
snbProgramma.ToonOverzicht();

Hier loopt iets mis. Benoem zelf de oorzaak en corrigeer de fout.

Commit je aanpassing.

Schooladmin project: Studieprogramma (stap 2)

Zoals boven, maar gebruik nu volgende code voor de demonstratiemethode:

Cursus communicatie = new Cursus("Communicatie");
Cursus programmeren = new Cursus("Programmeren");
Cursus databanken = new Cursus("Databanken", new Student[7], 5);
Cursus[] cursussen1 = { communicatie, programmeren, databanken };
Cursus[] cursussen2 = { communicatie, programmeren, databanken };
StudieProgramma programmerenProgramma = new StudieProgramma("Programmeren");
StudieProgramma snbProgramma = new StudieProgramma("Systeem- en netwerkbeheer");
programmerenProgramma.cursussen = cursussen1;
snbProgramma.cursussen = cursussen2;
// later wordt Databanken geschrapt uit het programma SNB
// voor SNB wordt bovendien Programmeren hernoemd naar Scripting
snbProgramma.cursussen[2] = null;
snbProgramma.cursussen[1].Titel = "Scripting";
programmerenProgramma.ToonOverzicht();
snbProgramma.ToonOverzicht();

Opnieuw loopt het fout. Benoem zelf de oorzaak en corrigeer de fout.

Commit je aanpassing.

List

In onderstaande kennisclip wordt er een andere syntax gebruikt om objecten aan te maken dan wat wij gewoon zijn (namelijk constructors met parameters). Dat maakt geen verschil voor de werking van de datastructuur. Je zou net zo goed een constructor kunnen definiëren die hetzelfde doet.

Een List<> collectie is de meest standaard collectie die je kan beschouwen als een flexibelere variant op een een doodnormale array.

De Generieke List<> klasse bevindt zich in de System.Collections.Generic namespace. Je dient deze namespace dus als using bovenaan toe te voegen wil je deze klasse kunnen gebruiken.

List aanmaken

De klasse List<T> is een zogenaamde generieke klasse. Tussen de < >tekens plaatsen we het type dat de lijst zal moeten gaan bevatten. Vaak wordt dit genoteerd als T voor "type". Bijvoorbeeld:

  • List<int> alleGetallen= new List<int>();

  • List<bool> binaryList = new List<bool>();

  • List<Pokemon> pokeDex = new List<Pokemon>();

  • List<string[]> listOfStringarrays = new List<string[]>();

Zoals je ziet hoeven we bij het aanmaken van een List geen begingrootte mee te geven, wat we wel bij arrays moeten doen. Dit is een van de voordelen van List: ze groeien mee. Als we toch een begingrootte meegeven (zoals in de kennisclip even getoond wordt) is dat enkel om de performantie van de code wat te verhogen in bepaalde scenario's. Wij gaan dit nooit doen.

Elementen toevoegen

Via de Add() methode kan je elementen toevoegen aan de lijst. Je dient als parameter aan de methode mee te geven wat je aan de lijst wenst toe te voegen. Deze parameter moet uiteraard van het type zijn dat de List verwacht.

In volgende voorbeeld maken we een List aan die objecten van het type string mag bevatten en vervolgens plaatsen we er twee elementen in.

Elementen indexeren

Het leuke van een List is dat je deze ook kan gebruiken als een gewone array, waarbij je met de indexer elementen kan aanspreken. Stel bijvoorbeeld dat we een lijst hebben met minstens 4 strings in. Volgende code toont hoe we de string op positie 3 kunnen uitlezen en hoe we die op positie 2 overschrijven:

Ook de klassieke werking met for blijft gelden. De enige aanpassing is dat List<T> niet met Length werkt maar met Count.

Wat kan een List nog?

Interessante methoden en properties voorts zijn:

  • Clear() :methode die de volledige lijst leegmaakt

  • Insert(): methode om element op specifieke plaats in lijst toe te voegen, bijvoorbeeld:

    voegt de string toe op de tweede plek en schuift de rest naar achter

  • Contains(): geef als parameter een specifiek object mee (van het type T dat de List<T> bevat) om te weten te komen of dat specifieke object in de List<> terug te vinden is. Indien ja dan zal true worden teruggeven.

  • IndexOf(): geeft de index terug van het element item in de rij. Indien deze niet in de lijst aanwezig is dan wordt -1 teruggegeven.

  • RemoveAt(): verwijder een element op de index die je als parameter meegeeft.

  • Remove(): verwijder het gegeven element

Foreach loops

Je kan met een eenvoudige for of while-loop over een lijst itereren, maar het gebruik van een foreach-loop is toch handiger.

Dit is dan ook de meestgebruikte operatie om eenvoudig en snel een bepaald stuk code toe te passen op ieder element van de lijst:

Verdere datastructuren

HashSet en ImmutableHashSet

In sommige situaties wil je dat een element geen twee keer in een datastructuur terecht kan komen. Je wil bijvoorbeeld dat de lijst met cursussen die deel uitmaakt van een studieprogramma geen tweemaal dezelfde cursus kan bevatten.

In dit geval gebruik je geen List<T>, maar een HashSet<T>. Elementen toevoegen doe je met de methode Add en elementen verwijderen doe je met Remove. Ook hier beperken we ons voorlopig tot voorgedefinieerde soorten objecten. Wanneer we System.Object hebben bestudeerd, kunnen we ook HashSets van onze eigen types maken.

De immutable variant is ImmutableHashSet.

Queue

Een Queue is een collectie van elementen die in een welbepaalde volgorde behandeld moeten worden: van voor naar achter. Vergelijk met een wachtrij bij de bakker: de klant die eerst in de rij staat wordt eerst geholpen, dan steeds de volgende klant tot we aankomen bij de klant die het laatst in de rij is aangesloten. We noemen dit soort van collectie ook wel een First In, First Out oftewel FIFO-collectie: het item dat eerst in de rij is gezet, is ook het eerste dat behandeld wordt.

Een Queue is dus een speciaal soort lijst, waarbij het toevoegen en verwijderen van elementen op de lijst niet op gelijk welke plaats mag gebeuren. Een queue biedt daarom geen Add() of RemoveAt() methode aan. In plaats daarvan gebruik je:

  • Enqueue(T item) om een item aan de rij toe te voegen

  • Dequeue() om een item uit de rij te halen. Deze methode geeft als returnwaarde het weggehaalde item terug, zodat je er iets mee kan doen.

  • Peek() geeft je het eerstvolgende item terug, maar verwijdert het nog niet uit de rij.

Voorbeeld

Op lijn 18 wordt de volgende klant uit de rij gehaald. Deze klant gebruiken we nog snel om zijn naam te tonen aan de gebruiker, maar na lijn 29 zal deze klant verdwijnen. Wil je deze klant in meer dan één statement gebruiken, zal je hem dus moeten opslaan in een lokale variabele:

Op lijn 20 wordt er eerst 'gespiekt' wie de volgende klant is: Piet. Met Peek() wordt hij echter nog niet uit de rij gehaald, zoals je in onderstaande output kan zien.

ImmutableQueue

De immutablevariant van Queue is ImmutableQueue.

Stack

Het omgekeerde van een Queue is een Stack. Dit is een lijst van items waarbij je steeds het laatst toegevoegde item eerst wilt behandelen. Vergelijk dit met een stapel borden aan de afwas: het eerstvolgende bord dat je afwast, is het bovenste bord op de stapel, dus het laatst toegevoegde. Of met een rij wagens in een lange, smalle garage met maar één toegangspoort: de eerste wagen die kan buitenrijden, is degene die laatst is binnengereden.

Dit noemen we een LIFO-collectie, oftewel Last In, First Out. Waar Queue Enqueue(T item) en Dequeue() gebruikte om items toe te voegen en uit de rij te halen, gebruikt Stack

  • Push(T item) om een item op de stapel te leggen.

  • Pop() om een item van de stapel te nemen.

  • Peek() om het bovenste item op de stapel te bekijken, zonder het er af te nemen.

Voorbeeld

Dit voorbeeld demonstreert de werking van de 'Maak ongedaan' functionaliteit die je hebt in de meeste programma's op je computer. Als je op 'Maak ongedaan' (Engels: undo, commando: Ctrl+Z) klikt, wordt enkel dat wat je als laatste gedaan hebt, teruggedraaid.

Volgend filmpje demonstreert de acties die de gebruiker uitvoert in een tekstbewerkingsprogramma:

De gebruiker neemt volgende stappen, vertrekkende vanaf een wit blad:

  1. Voeg paragraaf toe

  2. Zet tekst in vet

  3. Haal stuk tekst weg

  4. Maak laatste actie ongedaan

  5. Maak tekst groter.

  6. Maak laatste actie ongedaan

  7. Maak tekst kleiner.

  8. Voeg tekst toe.

De code om deze acties bij te houden in een actiehistoriek zou kunnen zijn:

Dit geeft volgende output:

ImmutableStack

De immutablevariant van Stack is ImmutableStack.

Dictionary

Naast de generieke List collectie, zijn er nog enkele andere nuttige generieke 'collectie-klassen' die je geregeld in je projecten kan gebruiken.

In een dictionary wordt ieder element voorgesteld door een sleutel (key) en de waarde (value) van het element. Het idee is dat je de sleutel kan gebruiken om de waarde snel op te zoeken. De sleutel moet dan ook uniek zijn. Dictionaries stellen geen reeks met een volgorde voor, maar geven je de mogelijkheid data met elkaar in verband te brengen.

Enkele voorbeeldjes die het idee achter Dictionary kunnen verduidelijken:

  • een papieren telefoonboek is als een Dictionary met gecombineerde namen en adressen als keys en telefoonnummers als values

  • een echt woordenboek is als een Dictionary met woorden als keys en omschrijvingen als values

  • een array is als een Dictionary met getallen als keys en waarden van het type van de array als values

Bij de declaratie van de Dictionary<K,V> dien je op te geven wat het datatype van de key zal zijn , alsook het type van de waarde (value). Met andere woorden, K en V komen niet letterlijk voor, maar je vervangt ze door types die je al kent.

Voor bovenstaande voorbeelden:

  • een echt woordenboek stel je best voor met string als type van de key en string als type van de value, want een woord stel je voor als string en een omschrijving ook

  • een array van double kan je nabootsen door uint te gebruiken als type van de key en double als type van de value

  • het telefoonboek moeten we wat vereenvoudigen, maar als we op basis van naam meteen een telefoonnummer konden opzoeken (zonder adresgegevens,...), dan zou string (de naam) het type van de key zijn en string (telefoonnummer) het type van de value. Het telefoonnummer is geen getal omwille van zaken die je niet met een getaltype kan voorstellen zoals +32 of 0473.

Gebruik Dictionary

In het volgende voorbeeld maken we een Dictionary van klanten aan. Iedere klant heeft een unieke ID (de key, die we als int gebruiken) alsook een naam (die niet noodzakelijk uniek is en de waarde voorstelt):

Bij de declaratie van customers plaatsen we dus tussen de < > twee datatypes: het eerste duidt het datatype van de key aan, het tweede dat van de values.

Merk op dat je niet verplicht bent om een int als type van de key (of value) te gebruiken, dit mag eender wat zijn, zelfs een klasse.

We kunnen nu met behulp van bijvoorbeeld een foreach-loop alle elementen tonen. Hier kunnen we de key met de .Key-property uitlezen en het achterliggende object of waarde met .Value. Value en Key hebben daarbij ieder het type dat we hebben gedefinieerd toen we het Dictionary-object aanmaakten, in het volgende geval is de Key dus van het type int en Value van het type string:

We kunnen echter ook een specifiek element opvragen aan de hand van de key. Stel dat we de waarde van de klant met key 123 willen tonen:

De key werkt dus net als de index bij gewone arrays, alleen heeft de key nu geen relatie meer met de positie van het element in de collectie.

Je kan de syntax met rechte haakjes ook gebruiken om een element toe te voegen. In tegenstelling tot Add, geeft deze syntax geen fout als de key al bestaat, maar vervangt hij het bestaande verband:

Als je wil weten of een bepaalde key voorkomt in een Dictionary, gebruik je de instantiemethode ContainsKey.

Cursussen wordt cursussen met een kleine letter en wordt private. Dit wordt aangegeven door het rode vierkantje in plaats van een groen bolletje.
De "C" bovenaan staat voor "class". De meeste programma's tekenen deze niet.
UML-klassendiagram voor Cursus

(met demonstratie in SchoolAdmin, zelf mee te maken!)

We hebben geleerd hoe we CSV-bestanden moesten uitlezen. Fris dit zo nodig op, want we gaan nu een stap verder.

NullReferenceException error in VS

Contains, Remove en IndexOf zullen zich met jouw eigen klassen niet noodzakelijk gedragen zoals je verwacht. De verklaring hierachter komt later aan bod, wanneer we bekijken. Ze zullen wel werken zoals verwacht voor voorgedefinieerde types, inclusief DateTime.

Bij dit laatste horen wel enkele nuances. Deze worden pas behandeld . Voorlopig zullen we alleen voorgedefinieerde types opnemen in dictionaries.

Kennisclip
eerder in de cursus
Kennisclip voor deze inhoud
List<String> myStringList = new List<String>();
myStringList.Add("This is the first item in my list!");
myStringList.Add("And another one!");
Console.WriteLine(myStringList[3]);
myStringList[2] = "andere zin";`
for(int i = 0 ; i < myStringList.Count; i++)
{
    Console.WriteLine(myStringList[i])
}
myStringList.Insert(1,"A fourth sentence");
List<int> integerList=new List<int>();
integerList.Add(2);
integerList.Add(3);
integerList.Add(7);

foreach(int prime in integerList)
{
   Console.WriteLine(prime);
}
public static void Bakker()
{
    Queue<string> klanten = new Queue<string>();

    klanten.Enqueue("Roos");
    klanten.Enqueue("Piet");
    klanten.Enqueue("Ellen");
    klanten.Enqueue("Frank");
    klanten.Enqueue("Oswald");

    Console.Write("De huidige wachtrij is: ");
    foreach (string klant in klanten)
    {
        Console.Write($"{klant} ");
    }
    Console.WriteLine('\n');

    Console.WriteLine($"We bedienen nu klant {klanten.Dequeue()}");
    klanten.Enqueue("Redouan"); //Nieuwe klant komt in de rij staan
    Console.WriteLine($"De volgende klant is {klanten.Peek()}\n");

    Console.Write("De huidige wachtrij is: ");
    foreach (string klant in klanten)
    {
        Console.Write($"{klant} ");
    }
    Console.WriteLine('\n');

    Console.WriteLine($"We bedienen nu klant {klanten.Dequeue()}\n");

    Console.Write("De huidige wachtrij is: ");
    foreach (string klant in klanten)
    {
        Console.Write($"{klant} ");
    }
    Console.WriteLine('\n');
}
//...
string volgendeKlant = klanten.Dequeue(); //sla de klant op in een lokale variabele
Console.WriteLine($"We bedienen nu klant {volgendeKlant}");
this.StuurFactuur(volgendeKlant); //klant "Roos" wordt nu ook in deze methodeoproep gebruikt.
//...
De huidige wachtrij is: Roos Piet Ellen Frank Oswald

We bedienen nu klant Roos
De volgende klant is Piet

De huidige wachtrij is: Piet Ellen Frank Oswald Redouan

We bedienen nu klant Piet

De huidige wachtrij is: Ellen Frank Oswald Redouan
public static void UndoDemo()
{
    Stack<string> acties = new Stack<string>();

    acties.Push("Voeg paragraaf toe.");
    acties.Push("Zet tekst in vet.");
    acties.Push("Haal stuk tekst weg.");

    Console.WriteLine("De actiehistoriek is: ");
    foreach (string actie in acties)
    {
        Console.WriteLine($"\t{actie} ");
    }
    Console.WriteLine('\n');

    Console.WriteLine($"Maak ongedaan: {acties.Pop()}\n");
    acties.Push("Maak tekst groter.");
    Console.WriteLine($"Maak ongedaan: {acties.Pop()}\n");

    acties.Push("Maak tekst kleiner.");

    Console.WriteLine($"De meest recente actie was: {acties.Peek()}\n");

    acties.Push("Voeg tekst toe.");

    Console.WriteLine("De actiehistoriek is: ");
    foreach (string actie in acties)
    {
        Console.WriteLine($"\t{actie} ");
    }
    Console.WriteLine('\n');
}
De actiehistoriek is:
        Haal stuk tekst weg.
        Zet tekst in vet.
        Voeg paragraaf toe.

Maak ongedaan: Haal stuk tekst weg.

Maak ongedaan: Maak tekst groter.

De meest recente actie was: Maak tekst kleiner.

De actiehistoriek is:
        Voeg tekst toe.
        Maak tekst kleiner.
        Zet tekst in vet.
        Voeg paragraaf toe.
Dictionary<int, string> customers = new Dictionary<int, string>();
customers.Add(123, "Tim Dams");
customers.Add(6463, "James Bond");
customers.Add(666, "The beast");
customers.Add(700, "James Bond");
Dictionary<int,Pokemon> pokedex;
Dictionary<Student,PuntenLijst> puntenTabel;
foreach (var item in customers){
    Console.WriteLine($"{item.Key}\t:{item.Value}");
}
Console.WriteLine(customers[123]);
// dit gaat, terwijl het met Add verboden is
customers[123] = "klant A";
customers[123] = "klant A, opnieuw";
Kennisclip voor deze inhoud
Equals en GetHashCode
Kennisclip voor deze inhoud
in een later hoofdstuk

Foreach en var

Foreach loops

Wanneer je geen indexering nodig hebt, maar toch snel over alle elementen in een array wenst te gaan, dan is het foreach statement een zeer nuttig is. Een foreach loop zal ieder element in de array een voor een in een tijdelijke variabele plaatsen (de iteration variable). Volgende code toont de werking waarbij we een array van strings hebben en alle elementen er in op het scherm willen tonen:

string[] boodschappen= {"ontbijtgranen", "koekjes", "fruit"};
foreach (string boodschap in boodschappen)
{
   Console.WriteLine($"Niet vergeten: {boodschap}");
}

De eerste keer dat we in de loop gaan zal het element boodschappen[0] aan boodschap toegewezen worden voor gebruik in de loop-body, vervolgens wordt boodschappen[1] toegewezen, enz.

Het voordeel is dat je dus geen teller/index nodig hebt en dat foreach zelf de lengte van de array zal bepalen.

Opgelet bij het gebruik van foreach loops

  • De foreach iteration variable is read-only: je kan dus geen waarden in de array aanpassen, enkel uitlezen.

  • De foreach gebruik je enkel als je alle elementen van een array wenst te benaderen. In alle andere gevallen zal je een ander soort loop (for, while, etc.) moeten gebruiken.

var keyword

C# heeft een var keyword. Je mag dit keyword gebruiken ter vervanging van het type (bv int) op voorwaarde dat de compiler kan achterhalen wat het type moet zijn.

var getal = 5; // var zal int zijn
var myArray = new double[20]; // var zal double[] zijn
var tekst = "Hi there handsome"; // var zal string zijn

Opgelet: het var keyword is in deze cursus nooit nodig. Het vergemakkelijkt het schrijfwerk, want het wordt door de compiler vertaald in een specifiek type. Er zijn scenario's waarin het wel nodig is, maar die zijn meer geavanceerd ("anonieme types").

Het betekent niet hetzelfde als de var van JavaScript. In JavaScript hoef je namelijk geen type vast te leggen voor variabelen en kan je dit doen:

var something = "hello";
something = 3;

In C# levert dit een compilatiefout. Met de eerste regel zeg je dat de compiler uit de rechterzijde mag afleiden dat var hier vervangen kan worden door string. Je kan geen waarde 3 in een variabele van type string plaatsen, dus dit levert een compilatiefout.

var en foreach

foreach (var boodschap in boodschappen)
{
   Console.WriteLine($"Niet vergeten: {boodschap}");
}

Immutable datastructuren

De standaard datastructuren van C# zijn reference types. Dit betekent dat iedereen die zo'n datastructuur te pakken krijgt (bijvoorbeeld omdat je hem als argument meegeeft aan een methode), de inhoud van deze datastructuur ook kan wijzigen. Dit kan met opzet of gewoonweg per vergissing gebeuren.

Hoezo, "met opzet"? Denk eraan dat je typisch niet de enige programmeur bent die met bepaalde code in contact komt.

Bijvoorbeeld:

// je gebruikt deze methode in de veronderstelling dat ze je data alleen maar print
// maar, zonder dat je het daarom meteen merkt, wist ze ook data
public static void PrintData(List<string> data) {
    for (int i = 0; i < data.Count; i++) {
        Console.WriteLine(data[i]);
        data[i] = null;
    }
}

In het algemeen geldt: als iemand bepaalde mogelijkheden niet echt nodig heeft, geef ze dan niet. Dit is opnieuw encapsulatie.

Om te verhinderen dat een datastructuur wordt aangepast, kan je er een immutable versie van maken. Dit is een versie van die datastructuur waarvan de inhoud achteraf niet gewijzigd kan worden. Er bestaan immutable versies van de standaard datastructuren en ze heten gewoonweg ImmutableList<T> en ImmutableDictionary<K,V>.

Om deze versies te gebruiken, moet je de System.Collections.Immutable namespace gebruiken. Wanneer je hier een using directief voor hebt staan, kan je methodes zoals ToImmutableList<T> oproepen op een lijst om er een immutable versie van te produceren. Deze immutable versie kan je dan veilig delen met code waarvan je niet wenst dat ze de inhoud van je lijst aanpast.

Een tweede manier om een immutable datastructuur te maken, is met een builder. Dit is een object waar je via Add data aan toevoegt en dat je achteraf vraagt een immutable list te produceren. Je kan er een aanmaken met de statische methode CreateBuilder van de immutable datastructuur die je wil gebruiken. Bijvoorbeeld:

public static void PrintData(ImmutableList<string> data) {
    foreach(var datum in data) {
        Console.WriteLine(datum);
    }
}

public static void DemonstreerImmutableListBuilder() {
    var builder = ImmutableList.CreateBuilder<string>();
    bool doorgaan;
    do {
        Console.WriteLine("Geef een element om toe te voegen");
        builder.Add(Console.ReadLine());
        Console.WriteLine("Doorgaan met elementen toevoegen?");
        doorgaan = Console.ReadLine().ToLower() == "ja";
    } while (doorgaan);
    PrintData(builder.ToImmutableList<string>());
}

het verschil met read-only properties

Beginnende programmeurs denken soms dat ze hetzelfde effect kunnen verkrijgen door een property voor een datastructuur "read only" te maken. Dit doen ze dan door alleen een getter te voorzien en geen setter of, als ze buiten deze cursus gaan zoeken, met het sleutelwoordje readonly.

Dit maakt je datastructuur niet immutable! Het zorgt er wel voor dat je het object op de heap waarin je data staat niet kan vervangen. Het zorgt er niet voor dat je de inhoud van dat object niet kan vervangen.

Bijvoorbeeld, als we personen voorzien van een array met lievelingsgerechten:

class Persoon {

    private List<string> lievelingsgerechten;
    public List<string> Lievelingsgerechten {
        get {
            return this.lievelingsgerechten;
        }
    }
    
    public Persoon(string gerecht1, string gerecht2, string gerecht3) {
        this.lievelingsgerechten = new List<string>();
        this.lievelingsgerechten.Add(gerecht1);
        this.lievelingsgerechten.Add(gerecht2);
        this.lievelingsgerechten.Add(gerecht3);
    }
}

class Program {
    public static void Main() {
        var persoon = new Persoon("spaghetti","koekjes","ijs");
        // dit gaat WEL:
        persoon.Lievelingsgerechten[1] = "lasagne";
        persoon.Lievelingsgerechten.Add("frieten");
        foreach(var gerecht in persoon.Lievelingsgerechten) {
            Console.WriteLine(gerecht);
        }
        // dit gaat NIET:
        // persoon.Lievelingsgerechten = new List<string>();
    }
}

Onderstaande figuur toont een vereenvoudigde weergave van wat er aan de hand is:

Voorbeeld van het gebruik van een tekstverwerker met undo-functionaliteit

Wanneer je de Visual Studio voor foreach gebruikt foreach [tab][tab] dan zal deze code ook een var gebruiken voor de iteration variabele. De compiler kan aan de te gebruiken array zien wat het type van een individueel element in de array moet zijn. De foreach van zonet kan dus herschreven worden naar:

Het is niet mogelijk de pijl van de persoon naar het lijstobject te vervangen. Het is wel mogelijk data in het lijstobject te veranderen.
code snippet