Zie Scherp Scherper - Hoofdstuk 18
Na dit hoofdstuk kan je:
werken met System.IO: paden, bestanden en folders
tekst lezen en schrijven met StreamReader, StreamWriter en de File-klasse
het using-blok inzetten om bestanden netjes vrij te geven
binaire bestanden verwerken met BinaryWriter en BinaryReader
info opvragen met FileInfo en DirectoryInfo
objecten serialiseren naar en deserialiseren uit JSON
Hiermee lees, schrijf, maak en verwijder je bestanden en folders.
Maar: je raakt nu echt de harde schijf.
Belangrijk
Tijd voor een back-up, en voor exception handling: bestanden luisteren minder goed dan jij. Eén foutje wist zomaar gegevens (ja, ook mij is dat al overkomen).
Darude Sandstorm for life.
Elk bestand heeft een uniek path, in C# steeds een string:
c:\
temp
mijnData.txt
Een path is niet hoofdlettergevoelig
Windows gebruikt \, macOS gebruikt /
Bouw paden platformonafhankelijk met de Path-klasse:
Tip
Path heeft ook GetTempPath, GetFileNameWithoutExtension, … En via Environment.GetFolderPath(Environment.SpecialFolder.Desktop) vind je het bureaublad van de gebruiker.
Controleer of iets bestaat vóór je het gebruikt, en overschrijf nooit zomaar:
Net als bij Console gebruik je WriteLine. De tweede parameter:
true toevoegen (append)
false overschrijft
Waarschuwing
Zo “bloot” gebruik je het beter niet: bij een fout blijft het bestand open en gelockt. Verderop lossen we dat op met using.
ReadLine geeft null zodra het einde van het bestand bereikt is: zo weet de loop wanneer te stoppen.
Een using-blok geeft een bestand gegarandeerd weer vrij, ook bij een fout:
Bij de sluitende accolade roept C# automatisch Dispose() aan: het bestand gaat netjes dicht.
Tip
Vanaf nu zetten we altijd een using rond een StreamWriter of StreamReader.
De File-klasse kan een bestand in één keer inlezen, zonder lus:
Klein bestand, in één keer
File.ReadAllText / ReadAllLines (kort en leesbaar)
Groot bestand of regel per regel
StreamReader met de while-lus
string path = "dagboek.txt";
Console.WriteLine("Dagboek:");
using (StreamReader reader = new StreamReader(path))
{
string regel;
while ((regel = reader.ReadLine()) != null)
Console.WriteLine(regel);
}
Console.WriteLine("Geef je volgende schrijfsel:");
string entry = Console.ReadLine();
using (StreamWriter writer = new StreamWriter(path, true))
{
writer.WriteLine($"\n{DateTime.Now}");
writer.WriteLine(entry);
}Met een BinaryWriter schrijf je elk datatype weg:
FileMode (Create, Append, Open, …) bepaalt hoe het bestand geopend wordt.
Write("Bond") ReadString()
Write(7) ReadInt32()
Write(true) ReadBoolean()
Belangrijk
De volgorde is cruciaal: lees in exact dezelfde volgorde als je schreef. Wissel je ReadInt32 en ReadBoolean om, dan crasht je programma met een EndOfStreamException.
Elk bestand is uiteindelijk gewoon een rij bytes. Toon ze als hexadecimaal:
Tip
{b:X2} toont elke byte als twee hexadecimale tekens. Zo zie je letterlijk wat een BinaryWriter op schijf zette.
Schrijf een waarde weg en kijk hoe groot het bestand wordt:
Tip
Probeer het met een bool, een int, een double, een char. Herken je de groottes uit hoofdstuk 2?
Een FileInfo-object geeft info over een bestand én bewerkingsmethoden:
CopyTo MoveTo Delete doen wat je verwacht.
Tip
File vs FileInfo? File is een static klasse (snel, kort iets doen). FileInfo instantieer je met new (handig voor meerdere bewerkingen na elkaar).
GetFiles en GetDirectories geven de inhoud terug
Een searchPattern (*.txt, Tim?.*) filtert
SearchOption.AllDirectories zoekt ook in subfolders
Met System.IO.Compression pak je hele mappen in of uit:
c:\temp\data
backup.zip
CreateFromDirectory
c:\temp\hersteld
ExtractToDirectory
Tip
Handig voor back-ups of om veel bestanden in een keer te versturen. Voeg using System.IO.Compression; toe.
Een bestand of API-antwoord ophalen doe je met HttpClient:
Opmerking
HttpClient is de moderne opvolger van het oudere WebClient. Het await hoort bij asynchroon programmeren (verdiepingsstof voor later).
Objecten met de hand wegschrijven (string-conversies, byte-volgorde) is lastig. Serialiseren doet het voor je: een object “in serie” naar een bestand en terug.
object
bestand
serialiseren
object
deserialiseren
Zo maak je letterlijk een savepoint van je programma.
JSON combineert het beste van beide werelden: compact én leesbaar.
Gebruik System.Text.Json. Serialiseren werkt op de publieke kant van je object:
student.json
Tip
Met new JsonSerializerOptions { WriteIndented = true } krijg je nette inspringing. Een hele List<Student> serialiseert net zo makkelijk.
De omgekeerde weg is generic: je geeft het verwachte type mee.
Opmerking
Met attributen stuur je het gedrag bij: [JsonIgnore] slaat een property over, [JsonPropertyName("...")] hernoemt ze, [JsonInclude] neemt iets privaat toch mee.
Onbekende JSON van bv. een web-API? Laat VS de klassen voor je schrijven:
kopieer de JSON
nieuw, leeg klasse-bestand
Edit - Paste Special - Paste JSON as Classes
BOEM!
System.IO geeft toegang tot de harde schijf: werk voorzichtig en met exception handling
StreamReader/StreamWriter (in een using-blok) en de File-klasse lezen en schrijven tekst
BinaryWriter/BinaryReader verwerken elk datatype, maar de volgorde telt
FileInfo en DirectoryInfo geven info en bewerkingen
JSON-serialisatie maakt een leesbaar savepoint van je objecten
Tip
En dat was het laatste hoofdstuk. Je bent van “Hello World!” tot serialisatie geraakt: dik verdiend. Blijf vooral oefenen en bouwen.
Kennisclips
Hoe doen andere programmeertalen hetzelfde? En een korte zoek-de-fout, om zo kritisch mogelijk naar code te leren kijken.
In Python doet with open exact wat ons using-blok doet: het bestand gaat automatisch dicht aan het einde van het blok, ook bij een fout.
Waar C# een StreamReader plus using nodig heeft, vangt Python beide in één korte regel.
JSON staat voor JavaScript Object Notation en komt dus letterlijk uit JavaScript. Geen aparte JsonSerializer nodig: serialiseren zit ingebouwd.
Nóg directer dan C#: omdat JSON uit JavaScript stamt, is een object en zijn JSON-voorstelling bijna hetzelfde ding.
Python gebruikt zijn ingebouwde json-module: json.dumps serialiseert, json.loads deserialiseert.
Verschil met C#: je hoeft geen type mee te geven. Python geeft een dict terug, waar C# via Deserialize<Student> weet bij welke klasse de data hoort.
Onderstaande Python-code schrijft een dagboekregel weg, maar het bestand blijft soms leeg of half geschreven, zeker als er verderop iets misloopt. Wat zou je beter doen?
Opmerking
Een handige samenvattende poster (Opgelet: gemaakt m.b.v. ChatGPT Image Gen 2).