• Een StreamWriter of StreamReader staat altijd in een using-blok. Zonder using blijft het bestand open, en wat de writer nog in zijn geheugen had, komt er misschien nooit in (uitleg).
  • Kijk met File.Exists of Directory.Exists voor je iets leest. Overschrijf of verwijder nooit een bestand zonder het eerst aan de gebruiker te vragen (uitleg).
  • Een bestand kan ontbreken, gelockt zijn of buiten je rechten vallen. Zet bestandswerk in een try met een catch per soort fout, de specifieke eerst: FileNotFoundException voor IOException (uitleg).
  • Bouw een pad met Path.Combine en Environment.GetFolderPath, niet met een vast pad zoals C:\Users\Steven\.... Dan werkt je programma ook op een andere pc en op een Mac (uitleg).

Is er plaats voor die game?

De nieuwe game is 85 GB groot, en de wifi op kot is traag. Je wil hem dus niet downloaden om halverwege te horen dat je schijf vol zit.

Met de klasse DriveInfo vraag je informatie op over de schijven van je computer. DriveInfo.GetDrives() geeft een array met alle schijven. Van elke schijf ken je onder andere:

  • Name: de naam, bv. C:\;
  • TotalSize: de totale grootte, in bytes;
  • AvailableFreeSpace: de vrije ruimte, in bytes;
  • IsReady: of je de schijf op dit moment kan uitlezen.

DriveInfo zit in de namespace System.IO, net als File en Directory.

Toon een genummerde lijst van alle schijven, te beginnen bij 1. Vraag de gebruiker over welke schijf hij meer wil weten, en toon van die schijf de vrije ruimte en de totale grootte in gigabytes, met twee cijfers na de komma. Vraag daarna hoe groot de game is, en zeg of hij past. Past hij niet, toon dan hoeveel GB je tekortkomt.

1. C:\
2. Z:\
Over welke schijf wil je meer weten?
>1
Schijf C:\: 584,85 GB vrij van 951,65 GB
Hoeveel GB is de game?
>85
De game past.

De gebruiker typt 1 voor de eerste schijf, maar in de array heeft die index 0.

Een schijf die niet klaar is, zoals een kaartlezer zonder kaart, geeft een IOException als je TotalSize opvraagt. Kijk dus eerst naar IsReady.

Op een Mac zie je geen stationsletters, maar een lijst mappen. De schijf / is degene die je zoekt.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • System.IO gaat over meer dan bestanden: ook schijven en mappen zitten erin.
  • Grootte staat in bytes. Deel door 1024.0 * 1024 * 1024 met een kommagetal erin, anders verdwijnt alles na de komma.
  • Wat buiten je programma ligt, is niet altijd klaar. Vraag het eerst (IsReady) voor je erop rekent.

Boekencollectie (Essential)

Maak een applicatie waarmee een gebruiker zijn boekencollectie beheert. De boeken worden bewaard in een tekstbestand boeken.txt. De gebruiker kan boeken toevoegen, verwijderen en de volledige lijst bekijken.

Potloodtekening: de robot zet boeken in een kast terwijl er een lange papieren rol met tekst uit de kast rolt, het stokmannetje leest.

Deel 1. Elke regel in boeken.txt is één boek, in het formaat Titel;Auteur;Jaar:

De Hobbit;J.R.R. Tolkien;1937
Harry Potter en de Steen der Wijzen;J.K. Rowling;1997
Het Leven van Pi;Yann Martel;2001
  • Bestaat boeken.txt nog niet bij het opstarten, dan maakt je programma het aan met deze drie boeken.
  • Geef enkel een bestandsnaam, zonder pad. Toon bij de start waar het bestand staat, met Path.GetFullPath("boeken.txt"). Zo vind je het terug en kan je het openen in Kladblok.
  • Daarna toont het programma telkens dit menu, tot de gebruiker 4 kiest:
    1. Een nieuw boek toevoegen
    2. Een boek verwijderen (de gebruiker geeft de exacte titel)
    3. Alle boeken weergeven
    4. Programma afsluiten
  • Een lege regel in het bestand slaat je over bij het weergeven.
Je boeken staan in C:\Users\student\Documents\Boekencollectie\bin\Debug\net10.0\boeken.txt

1. Nieuw boek toevoegen
2. Boek verwijderen
3. Alle boeken weergeven
4. Programma afsluiten
Maak een keuze:
>1
Titel:
>De Kleine Prins
Auteur:
>Antoine de Saint-Exupery
Jaar:
>1943
Boek toegevoegd!

1. Nieuw boek toevoegen
2. Boek verwijderen
3. Alle boeken weergeven
4. Programma afsluiten
Maak een keuze:
>2
Titel van het boek dat je wilt verwijderen:
>De Hobbit
Boek verwijderd!

1. Nieuw boek toevoegen
2. Boek verwijderen
3. Alle boeken weergeven
4. Programma afsluiten
Maak een keuze:
>3
Titel: Harry Potter en de Steen der Wijzen, Auteur: J.K. Rowling, Jaar: 1997
Titel: Het Leven van Pi, Auteur: Yann Martel, Jaar: 2001
Titel: De Kleine Prins, Auteur: Antoine de Saint-Exupery, Jaar: 1943

1. Nieuw boek toevoegen
2. Boek verwijderen
3. Alle boeken weergeven
4. Programma afsluiten
Maak een keuze:
>4

Het pad op de eerste regel hangt af van je pc. Met dotnet run (bv. in VS Code op een Mac) staat het bestand in je projectmap, niet in bin.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 2. Tijd voor wat je in de vorige hoofdstukken leerde. Een boek wordt een object van een klasse Boek, met de properties Titel, Auteur en Jaar (een int). Je programma werkt met een List<Boek> en bewaart die als JSON in boeken.json.

  • Bij de start lees je boeken.json in met JsonSerializer.Deserialize<List<Boek>>. Bestaat het bestand nog niet, dan begin je met de drie voorbeeldboeken.
  • Het menu blijft hetzelfde, maar het werkt nu op de lijst. Pas bij keuze 4 schrijf je de hele lijst weg, met WriteIndented.
  • Keuze 3 toont de boeken van oud naar nieuw. Laat Boek daarvoor IComparable implementeren en roep Sort() op de lijst op.
  • Typt de gebruiker bij het jaar geen getal, dan wordt het boek niet toegevoegd en loopt het programma gewoon verder.

Na het toevoegen van De Kleine Prins (1943) toont keuze 3:

Titel: De Hobbit, Auteur: J.R.R. Tolkien, Jaar: 1937
Titel: De Kleine Prins, Auteur: Antoine de Saint-Exupery, Jaar: 1943
Titel: Harry Potter en de Steen der Wijzen, Auteur: J.K. Rowling, Jaar: 1997
Titel: Het Leven van Pi, Auteur: Yann Martel, Jaar: 2001

En na het verwijderen van De Hobbit en keuze 4 staat er in boeken.json:

[
  {
    "Titel": "De Kleine Prins",
    "Auteur": "Antoine de Saint-Exupery",
    "Jaar": 1943
  },
  {
    "Titel": "Harry Potter en de Steen der Wijzen",
    "Auteur": "J.K. Rowling",
    "Jaar": 1997
  },
  {
    "Titel": "Het Leven van Pi",
    "Auteur": "Yann Martel",
    "Jaar": 2001
  }
]

Wie boeken.json met de hand kapotmaakt, krijgt bij het inlezen een JsonException. Die zit in System.Text.Json, en je vangt ze op zoals elke andere exception.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Een bestandsnaam zonder pad komt in de map waar je programma draait. Path.GetFullPath toont waar dat is. Zie Bestandslocatie zonder path.
  • Voor een klein bestand volstaan File.ReadAllLines, File.WriteAllLines en File.AppendAllText. Zie File.ReadAllText of StreamReader?.
  • Een regel uit een bestand kan leeg of anders zijn dan je verwacht. Kijk na hoeveel stukken Split teruggeeft voor je ze gebruikt.
  • Met JsonSerializer bewaar je een hele lijst objecten in één keer, en lees je ze in één keer terug. Zie Serialiseren in C# naar JSON.

Stevens stille fouten (Essential)

Stagiair Steven kreeg drie kleine opdrachten met bestanden. Hij liet ze alle drie door een A.I. schrijven en keek niets na: het compileerde, en er crashte niets. Toch klopt er in geen van de drie iets van.

Zet elk fragment in de Main van een nieuw project. De bestanden krijgen enkel een naam en geen pad, zodat ze naast je programma terechtkomen, op Windows en op Mac. Vind je een bestand niet terug, laat dan bijvoorbeeld Path.GetFullPath("logboek.txt") afdrukken.

Voorspel telkens eerst wat er gebeurt, en voer het fragment pas daarna uit.

Deel 1. Steven houdt een logboek bij van wie zijn programma gebruikt:

StreamWriter writer = new StreamWriter("logboek.txt", true);
writer.WriteLine("Programma gestart");

Console.WriteLine("Hoe heet je?");
string naam = Console.ReadLine();
writer.WriteLine($"Gebruiker: {naam}");

Console.WriteLine($"Welkom, {naam}!");

Voer het twee keer uit, met twee verschillende namen. Hoe groot is logboek.txt daarna, wat staat erin, en waarom?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 2. Steven bewaart de instellingen van zijn spel in een binair bestand: de naam van de speler, het aantal levens en of het geluid aan staat. Het inlezen vroeg hij een dag later aan de A.I., in een nieuw gesprek:

// bewaren
FileStream fsSchrijven = File.Open("instellingen.dat", FileMode.Create);
using (BinaryWriter writer = new BinaryWriter(fsSchrijven))
{
    writer.Write("Steven");
    writer.Write(3);
    writer.Write(false);
}

// later: terug inlezen
FileStream fsLezen = File.Open("instellingen.dat", FileMode.Open);
using (BinaryReader reader = new BinaryReader(fsLezen))
{
    string speler = reader.ReadString();
    bool geluidAan = reader.ReadBoolean();
    int levens = reader.ReadInt32();
    Console.WriteLine($"{speler} heeft {levens} levens. Geluid aan = {geluidAan}");
}

Wat verschijnt er op het scherm? En waarom crasht het programma niet?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 3. Steven wil zijn huisdier bewaren als JSON. De A.I. maakte een klasse in een eigen bestand, Huisdier.cs:

public class Huisdier
{
    private string naam;
    private int leeftijd;

    public Huisdier(string naam, int leeftijd)
    {
        this.naam = naam;
        this.leeftijd = leeftijd;
    }

    public override string ToString()
    {
        return $"{naam} ({leeftijd} jaar)";
    }
}

In Main, met using System.Text.Json; bovenaan Program.cs:

Huisdier kat = new Huisdier("Mimi", 4);
Console.WriteLine(kat);

string json = JsonSerializer.Serialize(kat);
File.WriteAllText("huisdier.json", json);
Console.WriteLine("Huisdier bewaard.");

Op het scherm staat Mimi (4 jaar) en Huisdier bewaard.. Wat staat er in huisdier.json, en waarom? Pas de klasse aan zodat de naam en de leeftijd wél in het bestand komen.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Geen van de drie fouten geeft een foutmelding of een crash. Bij deel 1 en 3 ziet wie enkel naar het scherm kijkt zelfs niets verdachts. Open na het uitvoeren dus ook het bestand zelf.
  • Een StreamWriter zet je altijd in een using-blok. Zie using alternatief.
  • Een binair bestand onthoudt geen datatypes. Lees in exact dezelfde volgorde als je schreef. Zie BinaryReader.
  • Enkel publieke properties komen in de JSON, tenzij je JsonInclude gebruikt. Zie Serialiseren in C# naar JSON en JsonInclude.

Het lessenrooster crasht (Essential)

Je bouwt een app die je lessenrooster uit het bestand rooster.txt leest. Bij elke poging crasht ze, telkens met een andere exception.

Deel 1. Zet elk fragment apart in de Main van een nieuw project, in een map waar nog geen rooster.txt staat. Voorspel eerst: crasht het, en zo ja, met welke exception? Voer het daarna uit en lees de eerste regel van de foutmelding.

// fragment 1
string rooster = File.ReadAllText("rooster.txt");
Console.WriteLine(rooster);
// fragment 2
string pad = Path.Combine("semester2", "rooster.txt");
File.WriteAllText(pad, "Maandag: Programming Principles");
Console.WriteLine("Rooster bewaard.");
// fragment 3
File.Create("rooster.txt");
File.WriteAllText("rooster.txt", "Maandag: Programming Principles");
Console.WriteLine("Rooster bewaard.");
// fragment 4: voer dit twee keer na elkaar uit
File.WriteAllText("rooster.txt", "Maandag: Programming Principles");
FileInfo info = new FileInfo("rooster.txt");
info.CopyTo("rooster_backup.txt");
Console.WriteLine("Backup gemaakt.");

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 2. Schrijf nu het programma dat niet meer crasht. Het vraagt welk rooster de gebruiker wil zien, leest dat bestand met File.ReadAllText en toont het. Elke fout geeft een eigen, gewone zin:

Exception Wat de gebruiker ziet
FileNotFoundException Dat rooster bestaat niet.
DirectoryNotFoundException Die map bestaat niet.
UnauthorizedAccessException Je hebt geen rechten om dat rooster te lezen.
IOException (alle andere problemen) Het rooster is niet te lezen: met de Message van de exception erachter

Maak voor je test een rooster.txt met één regel in, Maandag: Programming Principles, en zet het naast je programma. Waar dat is, toont Path.GetFullPath("rooster.txt"). Drie keer uitvoeren:

Welk rooster wil je zien?
>rooster.txt
Maandag: Programming Principles
Welk rooster wil je zien?
>examens.txt
Dat rooster bestaat niet.
Welk rooster wil je zien?
>semester2/rooster.txt
Die map bestaat niet.

Zet daarna het catch-blok van IOException bovenaan, voor de andere. Wat zegt de compiler, en waarom?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Elke soort fout met bestanden heeft een eigen exception. Lees de eerste regel van de foutmelding: daar staat welke.
  • File.Create houdt het bestand open. Sluit het met een using. Zie Folder of bestand aanmaken.
  • FileNotFoundException en DirectoryNotFoundException zijn een IOException. Hun catch komt dus voor die van IOException.
  • Een gebruiker krijgt een gewone zin te zien, geen foutmelding van .NET.

Downloadmap na de blok (Essential)

Na de examens staat je map Downloads vol: cursussen als pdf, foto’s van de cantus en het kotfeest, een installatiebestand, en een paar downloads die mislukten en 0 bytes groot zijn. Tijd om op te ruimen.

Werk nooit op je echte map Downloads. Een fout in je code kan bestanden verplaatsen of verwijderen, en dat draai je niet terug. Je programma maakt daarom eerst een testmap aan met nepbestanden. Zet deze twee methoden in je Program en roep MaakTestmap op als eerste regel van je Main:

static void MaakBestand(string map, string naam, int grootte)
{
    File.WriteAllBytes(Path.Combine(map, naam), new byte[grootte]);
}

static string MaakTestmap()
{
    string map = Path.Combine(Path.GetTempPath(), "downloads-test");
    Directory.CreateDirectory(map);
    MaakBestand(map, "H17_Interfaces.pdf", 2400000);
    MaakBestand(map, "H18_Bestanden.pdf", 3100000);
    MaakBestand(map, "lessenrooster_semester2.pdf", 85000);
    MaakBestand(map, "slides_H16.pdf", 0);
    MaakBestand(map, "cantus_01.jpg", 1250000);
    MaakBestand(map, "kotfeest_01.jpg", 980000);
    MaakBestand(map, "kotfeest_02.jpg", 0);
    MaakBestand(map, "VisualStudioSetup.exe", 4200000);
    return map;
}

MaakTestmap geeft het volledige pad van de testmap terug. Toon dat pad, zodat je de map kan openen in Verkenner of Finder en kan meekijken.

Werk daarna met een DirectoryInfo van de testmap:

  1. Toon van elk bestand de naam en de grootte in kB (Length / 1024).
  2. Zoek de bestanden van 0 bytes, toon hun namen en vraag of ze weg mogen. Enkel als de gebruiker j typt, verwijder je ze met Delete.
  3. Verplaats alle pdf’s naar een submap Cursussen en alle jpg’s naar een submap Fotos. Gebruik GetFiles("*.pdf") en GetFiles("*.jpg"), en maak de submap aan als ze nog niet bestaat.
  4. Toon tot slot opnieuw wat er nog in de testmap staat.

Voer je het programma een tweede keer uit, dan maakt MaakTestmap de bestanden opnieuw aan, terwijl er al exemplaren in Cursussen en Fotos staan. Je programma mag dan niet crashen: staat een bestand al in de submap, dan meld je dat en laat je het staan.

Testmap: C:\Users\student\AppData\Local\Temp\downloads-test

In downloads-test:
cantus_01.jpg (1220 kB)
H17_Interfaces.pdf (2343 kB)
H18_Bestanden.pdf (3027 kB)
kotfeest_01.jpg (957 kB)
kotfeest_02.jpg (0 kB)
lessenrooster_semester2.pdf (83 kB)
slides_H16.pdf (0 kB)
VisualStudioSetup.exe (4101 kB)

Lege bestanden:
kotfeest_02.jpg
slides_H16.pdf
Mogen deze 2 bestanden weg? (j/n)
>j
Verwijderd.
H17_Interfaces.pdf -> Cursussen
H18_Bestanden.pdf -> Cursussen
lessenrooster_semester2.pdf -> Cursussen
cantus_01.jpg -> Fotos
kotfeest_01.jpg -> Fotos

In downloads-test:
VisualStudioSetup.exe (4101 kB)

Op een Mac staat de testmap ergens onder /var/folders/, en de bestanden kunnen in een andere volgorde verschijnen.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Een DirectoryInfo geeft met GetFiles() een array van FileInfo-objecten. Elk daarvan kent zijn Name, Length en Extension. Zie GetFiles en GetDirectories.
  • Met een zoekpatroon zoals "*.pdf" filter je meteen. Zie Filteren op bestanden.
  • MoveTo en Delete werken echt op je schijf, zonder prullenbak. Test in een eigen testmap, en vraag het aan de gebruiker voor je iets verwijdert. Zie Kopiëren, verplaatsen en verwijderen.
  • Path.GetTempPath() geeft een map voor tijdelijke bestanden, op elke pc een andere. Zie System.IO.Path.

Filmavond (Essential)

Elke week dezelfde discussie: welke film kijken we vanavond? Jullie spreken af om enkel nog te kiezen uit de top 100 van IMDB. Jij schrijft het programma dat de lijst toont, een titel zoekt en een watchlist bijhoudt die blijft bestaan als je het programma afsluit.

De lijst staat als JSON-bestand op deze pagina. Open de link en bewaar de pagina met Opslaan als als movies.json op je bureaublad. Zo begint het bestand:

[
    {
        "title": "The Shawshank Redemption",
        "rank": "1",
        "id": "tt0111161"
    },
    {
        "title": "The Godfather",
        "rank": "2",
        "id": "tt0068646"
    },

Let op drie zaken:

  • Het bestand begint met [: het is een lijst. Je leest het dus in met JsonSerializer.Deserialize<List<Film>>.
  • De namen beginnen met een kleine letter. Je properties beginnen met een hoofdletter en heten in het Nederlands. Koppel ze met JsonPropertyName. Zonder dat attribuut blijft elke property null, en crasht er niets.
  • rank staat tussen aanhalingstekens: het is tekst, geen getal. Maak er een string van.

Schrijf de klasse Film zelf. Paste JSON as Classes uit Visual Studio maakt hier klassen die niet werken, want het JSON-bestand begint met [.

Het programma:

  • Bouwt het pad naar movies.json met Environment.GetFolderPath en Path.Combine. Vindt het het bestand niet, dan zegt het dat en stopt het.
  • Toont een menu tot de gebruiker 5 kiest:
    1. Top 100 tonen, als 1. The Shawshank Redemption, 2. The Godfather, …
    2. Titel zoeken: de gebruiker typt een titel of een deel ervan, het programma toont elke film die het bevat, zonder onderscheid tussen hoofdletters en kleine letters.
    3. Film op de watchlist zetten: de gebruiker typt een plaats van 1 tot 100.
    4. Watchlist tonen.
    5. Afsluiten.
  • Bewaart de watchlist meteen na elke toevoeging in watchlist.json, naast het programma, en leest ze bij de start terug in als het bestand bestaat.
100 films ingelezen, 0 op de watchlist.

1. Top 100 tonen
2. Titel zoeken
3. Film op de watchlist zetten
4. Watchlist tonen
5. Afsluiten
Maak een keuze:
>2
Welke titel zoek je?
>lord
9. The Lord of the Rings: The Return of the King
12. The Lord of the Rings: The Fellowship of the Ring
20. The Lord of the Rings: The Two Towers

1. Top 100 tonen
2. Titel zoeken
3. Film op de watchlist zetten
4. Watchlist tonen
5. Afsluiten
Maak een keuze:
>3
Welke plaats (1-100)?
>12
The Lord of the Rings: The Fellowship of the Ring staat op de watchlist.

1. Top 100 tonen
2. Titel zoeken
3. Film op de watchlist zetten
4. Watchlist tonen
5. Afsluiten
Maak een keuze:
>4
12. The Lord of the Rings: The Fellowship of the Ring

1. Top 100 tonen
2. Titel zoeken
3. Film op de watchlist zetten
4. Watchlist tonen
5. Afsluiten
Maak een keuze:
>5

Geef Film daarna een read-only property Url, die de link naar de IMDB-pagina teruggeeft: https://www.imdb.com/title/ met de Id en een / erachter. Open watchlist.json na een nieuwe toevoeging. Wat zie je erbij staan? Zorg dat Url niet meer in het bestand komt.

PRO. Voeg keuze 6 toe: de gebruiker typt een plaats, en het programma opent de IMDB-pagina van die film in de browser. Dat staat niet in het boek. Je hebt deze regel nodig, met using System.Diagnostics; bovenaan:

Process.Start(new ProcessStartInfo(film.Url) { UseShellExecute = true });

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • De deserializer koppelt op naam. Kloppen de namen niet, dan krijg je geen fout, maar lege properties. JsonPropertyName legt de koppeling. Zie JsonPropertyName.
  • Een JSON-bestand dat met [ begint, is een lijst: Deserialize<List<Film>>. Zie Deserialiseren in C# vanuit JSON.
  • Wat je kan uitrekenen, hoort niet in het bestand. JsonIgnore houdt het eruit. Zie JsonIgnore.
  • Een bestand op het bureaublad vind je op elke pc terug met Environment.GetFolderPath en Path.Combine. Zie Speciale folders.

De pixelmascottes

De mascottes van deze cursus, zoals stagiair Steven en de verteller, zijn pixelart. Je schrijft een programma dat uit een bmp-bestand leest hoe breed en hoe hoog een afbeelding is, en hoeveel bits elke pixel gebruikt. Je opent het bestand binair en leest enkel het begin: de header.

Download de twee mascottes: steven.bmp en verteller.bmp. De gebruiker typt het pad naar het bestand.

De eerste 30 bytes van een bmp-bestand:

Bytes Inhoud Grootte
0-1 Handtekening: de letters B en M 2 bytes
2-5 Bestandsgrootte 4 bytes
6-9 Gereserveerd 4 bytes
10-13 Plaats waar de pixels beginnen 4 bytes
14-17 Grootte van de rest van de header 4 bytes
18-21 Breedte in pixels 4 bytes, een int
22-25 Hoogte in pixels 4 bytes, een int
26-27 Aantal vlakken (planes) 2 bytes, een short
28-29 Bitdiepte: bits per pixel 2 bytes, een short

De getallen staan little-endian, net zoals een BinaryWriter ze wegschrijft. Een BinaryReader leest ze dus zonder omrekenen.

  • Lees eerst de twee bytes van de handtekening met ReadBytes(2). Zijn het geen B en M, dan zeg je dat het geen bmp-bestand is, en lees je niet verder.
  • Sla de bytes 2 tot en met 17 over door ze in te lezen zonder er iets mee te doen. ReadBytes krijgt als parameter hoeveel bytes het moet lezen.
  • Lees daarna in de juiste volgorde de breedte, de hoogte, de vlakken en de bitdiepte, met ReadInt32 en ReadInt16.
  • Vang de fouten op: een bestand dat niet bestaat, en een bestand dat te kort is.
Pad naar het bmp-bestand:
>C:\Users\student\Downloads\steven.bmp
Breedte: 392 pixels
Hoogte: 814 pixels
Bitdiepte: 24 bits per pixel

Met verteller.bmp krijg je 288 op 376 pixels, met 32 bits per pixel. Test ook met een png of een jpg van je eigen pc: dan moet je programma zeggen dat het geen bmp-bestand is.

Gebruik je in Windows Kopiëren als pad, dan staan er aanhalingstekens rond het pad. Haal die weg voor je het intypt, anders vindt je programma het bestand niet.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Een binair bestand lees je in de volgorde waarin het geschreven is. Wat je niet nodig hebt, lees je toch in, om erover te springen. Zie BinaryReader.
  • De extensie .bmp bewijst niets. De eerste bytes vertellen wat voor bestand het echt is.
  • Little-endian: de kleinste byte staat eerst. Zie Binaire inhoud tonen.

De Digitale Klokkenluider (Final Essentials, GPT)

Een klokkenluider heeft je een USB-stick gegeven van het bedrijf CorruptCorp. De stick zit vol mappen en submappen, en ergens daartussen zitten de bewijsstukken. Aan jou om de chaos in kaart te brengen en de belastende bestanden te vinden.

De stick en de bestanden erop zijn in het Engels, want CorruptCorp is een internationaal bedrijf. Je eigen code schrijf je in het Nederlands.

Stap 0. De stick. Je maakt de stick zelf na, als een map CorruptCorp naast je programma. Zet deze twee methoden in je Program en roep MaakStick("CorruptCorp"); op als eerste regel van je Main. Toon daarna Path.GetFullPath("CorruptCorp") en open die map in Verkenner of Finder.

static void MaakBestand(string map, string naam, string[] regels)
{
    Directory.CreateDirectory(map);
    File.WriteAllLines(Path.Combine(map, naam), regels);
}

static void MaakStick(string stick)
{
    MaakBestand(Path.Combine(stick, "Accounting", "Invoices"), "inv_1023.txt",
        new string[] { "Invoice #1023: Office Supplies", "Total: 245 EUR" });
    MaakBestand(Path.Combine(stick, "Accounting", "Invoices"), "inv_1024.txt",
        new string[] { "Invoice #1024: New Chairs", "Total: 1200 EUR" });
    MaakBestand(Path.Combine(stick, "Accounting", "Tax_Evasion_2024"), "payoff_mayor.log",
        new string[] { "2024-05-10: Meeting with Mayor", "2024-05-11: Dinner at Chez Pierre", "2024-05-12: Transfer 500k to Mayor - REASON: SECRET" });
    MaakBestand(Path.Combine(stick, "Accounting"), "q3_report.txt",
        new string[] { "Report for Q3: Everything looks good", "Profit: up 12 percent" });
    MaakBestand(Path.Combine(stick, "HR"), "lunch_menu.txt",
        new string[] { "Monday: Soup of the day", "Tuesday: Fries", "Wednesday: Fries again" });
    MaakBestand(Path.Combine(stick, "HR"), "meeting_notes.txt",
        new string[] { "Meeting notes: Promote recycling initiatives", "Action: buy more plants" });
    MaakBestand(Path.Combine(stick, "HR", "Complaints"), "complaint_404.txt",
        new string[] { "Complaint: Coffee machine is broken again" });
    MaakBestand(Path.Combine(stick, "IT", "Logs"), "server_log_jan.log",
        new string[] { "2024-01-15: Server restart success", "2024-01-16: Backup completed" });
    MaakBestand(Path.Combine(stick, "IT", "Logs"), "server_log_feb.log",
        new string[] { "2024-02-20: User password reset", "2024-02-21: Printer out of toner" });
    MaakBestand(Path.Combine(stick, "IT", "Logs"), "security_alert.txt",
        new string[] { "2024-03-30: FIREWALL BREACH DETECTED - IGNORED BY ADMIN" });
    MaakBestand(Path.Combine(stick, "Operations", "Warehouse"), "inventory.csv",
        new string[] { "item;units", "chairs;5000", "barrels;120" });
    MaakBestand(Path.Combine(stick, "Operations", "Warehouse"), "illegal_dumping.csv",
        new string[] { "date;action", "2024-06-01;Dump waste in river at midnight - TOXIC", "2024-06-15;Dump waste in river at midnight - TOXIC" });
    MaakBestand(Path.Combine(stick, "Legal", "Court Cases"), "case_552.txt",
        new string[] { "Case 552: Pending" });
    MaakBestand(Path.Combine(stick, "Legal", "Settlements"), "silence_agreement.docx",
        new string[] { "Settlement Agreement: Victim silence fee - 50k - SECRET" });
    MaakBestand(Path.Combine(stick, "Secret Projects", "Project X"), "bribe_senator.txt",
        new string[] { "To: Senator", "Subject: New contract", "Here is your BRIBE for the new contract" });
    MaakBestand(Path.Combine(stick, "Secret Projects", "Project Omega"), "panama_papers.log",
        new string[] { "2024-07-20: Hidden offshore account details - SECRET" });
    MaakBestand(Path.Combine(stick, "Secret Projects", "Project Omega"), "lab_results.txt",
        new string[] { "Experiment results: 99 percent failure rate - DANGEROUS" });
}

Path.Combine mag ook drie of meer stukken krijgen. File.WriteAllLines overschrijft een bestaand bestand, dus je kan het programma zo vaak uitvoeren als je wil.

Stap 1. De scanner. Schrijf een methode ScanMap(string map) die de map en al haar submappen doorzoekt. Schrijf ze zelf recursief, zoals in het boek, zonder SearchOption.AllDirectories. Voor elk bestand dat ze tegenkomt:

  • toont ze het pad en de grootte in bytes;
  • roept ze AnalyseerBestand op (stap 2) als de extensie .log, .csv of .txt is. Kijk naar de extensie zelf, met FileInfo.Extension. Een bestandsnaam die “log” bevat, zoals catalog.pdf, telt niet.

Houd bij hoeveel bestanden er op de stick staan, en hoeveel je er geanalyseerd hebt.

Stap 2. De analyse. Schrijf een methode AnalyseerBestand(string pad):

  • Ze leest het bestand regel per regel met een StreamReader, en houdt het regelnummer bij. De eerste regel is regel 1.
  • Ze zoekt in elke regel naar de woorden SECRET, BRIBE en TOXIC.
  • Staat er zo’n woord in, dan toont ze BEWIJS in, de bestandsnaam, het regelnummer en de regel, en zet ze die gegevens in een lijst met bewijsmateriaal.
  • Een bestand dat niet te lezen is, stopt de scan niet: toon een melding en ga verder met het volgende.

Stap 3. Het rapport. Staat er na de scan iets in de lijst, dan maak je op het bureaublad een map Bewijsmateriaal, met Environment.GetFolderPath en Path.Combine. Daarin schrijf je met een StreamWriter het bestand RAPPORT.txt, met:

  • de datum en het uur van vandaag;
  • het aantal bestanden op de stick, en het aantal dat je geanalyseerd hebt;
  • elke verdachte regel, met de bestandsnaam en het regelnummer.

Stap 4. De kopieën. Kopieer elk bestand waarin je bewijs vond met CopyTo naar de map Bewijsmateriaal, één keer per bestand. Staat het er al, dan laat je het staan.

De stick staat in C:\Users\student\Documents\Klokkenluider\bin\Debug\net10.0\CorruptCorp
CorruptCorp\Accounting\q3_report.txt (61 bytes)
CorruptCorp\Accounting\Invoices\inv_1023.txt (48 bytes)
CorruptCorp\Accounting\Invoices\inv_1024.txt (44 bytes)
CorruptCorp\Accounting\Tax_Evasion_2024\payoff_mayor.log (120 bytes)
  BEWIJS in payoff_mayor.log, regel 3: 2024-05-12: Transfer 500k to Mayor - REASON: SECRET
CorruptCorp\HR\lunch_menu.txt (65 bytes)
CorruptCorp\HR\meeting_notes.txt (71 bytes)
CorruptCorp\HR\Complaints\complaint_404.txt (43 bytes)
CorruptCorp\IT\Logs\security_alert.txt (57 bytes)
CorruptCorp\IT\Logs\server_log_feb.log (67 bytes)
CorruptCorp\IT\Logs\server_log_jan.log (66 bytes)
CorruptCorp\Legal\Court Cases\case_552.txt (19 bytes)
CorruptCorp\Legal\Settlements\silence_agreement.docx (57 bytes)
CorruptCorp\Operations\Warehouse\illegal_dumping.csv (117 bytes)
  BEWIJS in illegal_dumping.csv, regel 2: 2024-06-01;Dump waste in river at midnight - TOXIC
  BEWIJS in illegal_dumping.csv, regel 3: 2024-06-15;Dump waste in river at midnight - TOXIC
CorruptCorp\Operations\Warehouse\inventory.csv (38 bytes)
CorruptCorp\Secret Projects\Project Omega\lab_results.txt (57 bytes)
CorruptCorp\Secret Projects\Project Omega\panama_papers.log (54 bytes)
  BEWIJS in panama_papers.log, regel 1: 2024-07-20: Hidden offshore account details - SECRET
CorruptCorp\Secret Projects\Project X\bribe_senator.txt (77 bytes)
  BEWIJS in bribe_senator.txt, regel 3: Here is your BRIBE for the new contract

17 bestanden gevonden, 16 geanalyseerd, 5 verdachte regels.
Rapport geschreven: C:\Users\student\Desktop\Bewijsmateriaal\RAPPORT.txt

RAPPORT.txt:

Rapport CorruptCorp
Datum: 10/10/2026 17:15:11
Bestanden op de stick: 17
Geanalyseerde bestanden: 16
Verdachte regels:
- payoff_mayor.log, regel 3: 2024-05-12: Transfer 500k to Mayor - REASON: SECRET
- illegal_dumping.csv, regel 2: 2024-06-01;Dump waste in river at midnight - TOXIC
- illegal_dumping.csv, regel 3: 2024-06-15;Dump waste in river at midnight - TOXIC
- panama_papers.log, regel 1: 2024-07-20: Hidden offshore account details - SECRET
- bribe_senator.txt, regel 3: Here is your BRIBE for the new contract

De paden hangen af van je pc. Op een Mac staan er / in plaats van \, en kunnen de bestanden in een andere volgorde verschijnen.

Stap 5. De vraag. In silence_agreement.docx staat ook SECRET, maar je programma vond het niet. Waarom niet? Is dat erg?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Een recursieve methode roept zichzelf op voor elke submap, en stopt vanzelf als er geen submappen meer zijn. Zie Recursief een folderstructuur verwerken.
  • Regel per regel lezen doe je met een StreamReader in een using-blok. Zie StreamReader.
  • Een bestand dat niet leest, mag de rest niet tegenhouden: zet de try rond één bestand, niet rond de hele scan.
  • Schrijf je iets voor de gebruiker, bouw het pad dan met Environment.GetFolderPath en Path.Combine. Zie Speciale folders.
  • De extensie bepaalt wat je programma met een bestand doet, maar zegt niets zeker over wat erin staat.