Youtube: bestanden & excepties

Namespaces en klassen
In .NET zit de functionaliteit voor het werken met bestanden en mappen verspreid over heel wat namespaces en klassen.
Voor paden
Klasse | Omschrijving |
---|---|
System.IO.Path |
werken met paden; methodes Combine() , GetFileNameWithoutExtension() , GetDirectoryName() ... |
System.Environment |
omgevingsspecifieke paden; methodes GetFolderPath() , properties SpecialFolder.Desktop , SpecialFolder.MyDocuments ... |
Voor bestanden en mappen
De meeste klassen voor het werken met bestanden en mappen vind je in de System.IO
namespace:
Klasse | Omschrijving |
---|---|
Directory |
werken met mappen; methodes GetDirectories() , GetFiles() , Delete() , CreateDirectory() ... |
DirectoryInfo |
map informatie opvragen; properties FullName , Name , Parent , Exists ... |
File |
werken met bestanden; methodes ReadAllText() , WriteAllText() , Copy() , Delete() ... |
FileInfo |
bestand informatie opvragen; properties Name , Extension , Directory , Exists , Length ... |
StreamReader |
bestanden streamend lezen (geheugenvriendelijk, enkel nodig bij zeer grote bestanden) |
StreamWriter |
bestanden streamend schrijven (geheugenvriendelijk, enkel nodig bij zeer grote bestanden) |
System.IO.Path |
werken met paden; methodes Combine() , GetFileNameWithoutExtension() , GetDirectoryName() ... |
Voor dialoogvensters
Allerhande dialoogvensters vind je in Microsoft.Win32
, System.Windows
en System.Windows.Forms
:
Namespace | Klasse | Omschrijving |
---|---|---|
Microsoft.Win32 | OpenFileDialog |
dialoogvenster om bestanden te openen |
Microsoft.Win32 | OpenFolderDialog |
dialoogvenster om mappen te openen — pas beschikbaar vanaf .NET8! |
Microsoft.Win32 | SaveFileDialog |
dialoogvenster om bestanden op te slaan |
System.Windows | MessageBox |
foutmeldings-, waarschuwings- en berichtvensters |
System.Windows.Forms | FolderBrowserDialog |
ouderwets dialoogvenster om een map te kiezen |
Paden
Systeemmappen vinden
Op elke computer is de fysieke locatie voor systeemmappen als Desktop, My Documents... verschillend. Gebruik daarom altijd de Environment.SpecialFolder
enumeratie:
string desktopFolder = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
Andere typische voorbeelden zijn My Documents, My Pictures enz...:
Paden combineren
Gebruik best altijd System.IO.Path.Combine()
om paden te combineren:
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string filePath = System.IO.Path.Combine(folderPath, "textfiles\\myfile.txt");
Console.WriteLine(filePath);
C:\Users\rogie\Documents\textfiles\myfile.txt
Bestanden
Overzicht
Alle methodes om bestanden te lezen, schrijven, bewerken enz... vind je in de File
klasse:
Methode | Omschrijving |
---|---|
AppendAllLines() |
array of lijst toevoegen aan een bestand |
AppendAllText() |
tekst toevoegen aan een bestand |
Copy() |
kopieer een bestand |
Delete() |
verwijder een bestand |
Exists() |
controleert of een bestand bestaat |
Move() |
verplaats een bestand |
ReadAllLines() |
bestand inlezen in een string[] array |
ReadAllText() |
bestand inlezen in een string |
WriteAllLines() |
array of lijst keer schrijven naar een bestand |
WriteAllText() |
tekst schrijven naar een bestand |
Properties om bestandsinformatie op te vragen vind je in de FileInfo
klasse:
Property | Omschrijving |
---|---|
Directory |
map van het bestand (volledig pad) |
DirectoryName |
mapnaam van het bestand |
Exists |
bestaat het of niet (true of false ) |
Extension |
extensie (met punt) |
FullName |
volledig pad |
Length |
bestandsgrootte in bytes |
Name |
bestandsnaam |
Code voorbeelden
Bestand inlezen in een string
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string filePath = System.IO.Path.Combine(folderPath, "myfile.txt");
string inhoud = File.ReadAllText(filePath); // lees tekstinhoud bestand in
Bestand inlezen in een array
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string filePath = System.IO.Path.Combine(folderPath, "myfile.txt");
string[] regels = File.ReadAllLines(filePath); // lees regels bestand in
String schrijven naar een bestand
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string filePath = System.IO.Path.Combine(folderPath, "myfile.txt");
string inhoud = @"Dit is regel 1
Dit is regel 2";
File.WriteAllText(filePath, inhoud); // nieuw bestand als het nog niet bestaat
Array of lijst schrijven naar een bestand
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string filePath = System.IO.Path.Combine(folderPath, "myfile.txt");
string[] regels = new string[] { "Dit is lijn 1", "Dit is lijn 2" };
File.WriteAllLines(filePath, regels); // nieuw bestand als het nog niet bestaat
Tekst toevoegen aan een bestaand bestand
Naast File.WriteAllText()
en File.WriteAllLines()
bestaan er ook methodes File.AppendAllText()
en File.AppendAllLines()
:
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string filePath = System.IO.Path.Combine(folderPath, "myfile.txt");
File.AppendAllText(filePath, $"Am no an listening depending up believing{Environment.NewLine}");
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string filePath = System.IO.Path.Combine(folderPath, "myfile.txt");
string[] regels = new string[] { "Dit is lijn 3", "Dit is lijn 4" };
File.AppendAllLines(filePath, regels);
Controleren of een bestand bestaat
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string filePath = System.IO.Path.Combine(folderPath, "mycontacts.txt");
if (!File.Exists(filePath))
{
// ...
}
Informatie opvragen
Twee klassen bieden gedeeltelijk overlappende functionaliteit voor het opvragen van informatie: FileInfo
en Path
.
Codevoorbeeld met FileInfo
(bestand “test.txt” op de Desktop):
string filePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.txt");
FileInfo fi = new FileInfo(filePath);
Console.WriteLine($@"
bestandsnaam: {fi.Name}
extensie: {fi.Extension}
gemaakt op: {fi.CreationTime.ToString()}
mapnaam: {fi.Directory.Name}
");
bestandsnaam: test.txt extensie: .txt gemaakt op: 24/10/2023 19:45:16 mapnaam: Desktop
Codevoorbeeld met Path:
(bestand “test.txt” op de Desktop):
string filePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.txt");
Console.WriteLine($@"
bestandsnaam: {Path.GetFileName(filePath)}
bestandsnaam zonder extensie: {Path.GetFileNameWithoutExtension(filePath)}
extensie: {Path.GetExtension(filePath)}
map: {Path.GetDirectoryName(filePath)}
");
bestandsnaam: test.txt bestandsnaam zonder extensie: test extensie: .txt map: C:\Users\rogier\Desktop
Streamend lezen en schrijven
Alle vorige methodes laden de hele bestandsinhoud in één keer in het geheugen in. Voor heel grote bestanden is dat niet efficiënt. In die (uitzonderlijke) gevallen kan je gebruik maken van StreamReader
en StreamWriter
.
StreamReader
voorbeeld (zoeken naar een tekst):
List<string> foundLines = new List<string>();
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string filePath = System.IO.Path.Combine(folderPath, "mycontacts.txt");
// open stream and start reading
using (StreamReader reader = File.OpenText(filePath)) {
string line;
while ((line = reader.ReadLine()) != null) {
if (line.Contains("jennifer")) foundLines.Add(line);
}
} // stream closes automatically
StreamWriter
voorbeeld:
// prepare
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string filePath = System.IO.Path.Combine(folderPath, "myfile.txt");
// open stream and start writing
using (StreamWriter writer = File.CreateText(filePath)) {
writer.WriteLine("Dit is lijn 1");
writer.WriteLine("Dit is lijn 2");
} // stream closes automatically
In praktijk zul je streamend lezen of schrijven in deze cursus nooit nodig hebben.
Mappen
Overzicht
Alle methodes om te werken met mappen vind je in de Directory
klasse:
Methode | Omschrijving |
---|---|
CreateDirectory() |
maak een map aan |
Delete() |
verwijder een map |
Exists() |
controleert of een map bestaat |
GetDirectories() |
vraag een lijst submappen in de map op |
GetFiles() |
vraag een lijst bestanden in de map op |
Properties om mapinformatie op te vragen vind je in de DirectoryInfo
klasse:
Property | Omschrijving |
---|---|
Exists |
bestaat het of niet (true of false ) |
FullName |
volledig pad |
Name |
bestandsnaam |
Parent |
basismap (volledig pad) |
Code voorbeelden
Map inhoud lezen
Gebruik de methodes GetDirectories
en GetFiles
. Voorbeeld voor het lezen van de bestanden op de desktop:
string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string[] files = Directory.GetFiles(desktop); // lijst van bestanden (volledig pad)
foreach (string file in files)
{
Console.WriteLine(new FileInfo(file).Name); // geef enkel de namen weer
}
Controleren of een map bestaat
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string startfolder = System.IO.Path.Combine(folderPath, "myfolder");
if (!Directory.Exists(startfolder))
{
// ...
}
Informatie opvragen
Gebruik de DirectoryInfo
klasse om informatie op te vragen. Schematisch codevoorbeeld (map “Odisee” op de desktop):
string folderPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Odisee");
DirectoryInfo di = new DirectoryInfo(folderPath);
Console.WriteLine($@"
mapnaam: {di.Name}
basismap: {di.Parent.Name}
gemaakt op: {di.CreationTime}
");
mapnaam: Odisee basismap: Desktop gemaakt op: 29/01/2023 15:46:31
Dialoogvensters
OpenFileDialog
Schematisch:
OpenFileDialog dialog = new OpenFileDialog();
dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
dialog.Filter = "Tekstbestanden|*.TXT;*.TEXT";
string chosenFileName;
bool? dialogResult = dialog.ShowDialog();
if (dialogResult == true) {
// user picked a file and pressed OK
chosenFileName = dialog.FileName;
} else {
// user cancelled or escaped dialog window
}
OpenFolderDialog
Let op! Tot voor kort bestond (zeer vreemd genoeg) dit dialoogvenster niet. Het is pas toegevoegd sinds .NET8, dus werkt niet in oudere projecttypes als WPF .NET Framework! Daar werd je verondersteld third party oplossingen te gebruiken. Schematisch:
OpenFolderDialog dialog = new OpenFolderDialog();
dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string chosenFolderName;
bool? dialogResult = dialog.ShowDialog();
if (dialogResult == true)
{
// user picked a folder and pressed OK
chosenFolderName = dialog.FolderName;
}
else
{
// user cancelled or escaped dialog window
}
SaveFileDialog
Schematisch:
SaveFileDialog dialog = new SaveFileDialog();
dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
dialog.Filter = "Tekstbestanden|*.TXT;*.TEXT";
dialog.FileName = "savedfile.txt";
if (dialog.ShowDialog() == true) {
File.WriteAllText(dialog.FileName, "tekstinhoud hier");
} else {
// user pressed Cancel or escaped dialog window
}
MessageBox
Exception handling
Wanneer gebruiken
De regel wanneer wel en wanneer niet te gebruiken is heel eenvoudig:
Gebruik Exception handling enkel voor externe fouten waar je geen controle over hebt en die op geen andere manier op te vangen zijn, niet om mogelijke fouten in je eigen code weg te moffelen!
Gebruik het dus enkel in situaties waar je zelf echt niks anders aan kan doen:
- lezen/schrijven bestanden en mappen: I/O problemen, b.v. geen schrijfrechten
- databank operaties: kan offline zijn of niet reageren, er kan een fout optreden bij uitvoeren query...
- netwerk operaties: idem
- gebruik externe bibliotheken: kunnen fouten maken of zelf excepties genereren
- validatie externe data: bij parsen JSON, XML, input van de gebruiker...
- ...
Gebruik het niet wanneer er alternatieven bestaan:
- bestanden of mappen die niet gevonden worden (gebruik
File.Exists()
ofDirectory.Exists()
) - situaties die evengoed met een if-else kunnen opgevangen worden
- rond een codeblok waarvan je niet helemaal zeker bent dat er geen fouten in staan (schrijf gewoon betere code!)
- ...
Hoe gebruiken
Enkel indien echt nodig
Als er een evenwaardig alternatief bestaat, mag je het niet gebruiken. Twee voorbeelden:
try
{
int[] numbers = new int[3];
Console.WriteLine(numbers[5]); // Index out of bounds
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine("Handled out-of-bounds access");
}
int[] numbers = new int[3];
if (numbers.Length > 5)
{
Console.WriteLine(numbers[5]);
}
else
{
Console.WriteLine("Index out of range");
}
try
{
string content = File.ReadAllText("somefile.txt");
}
catch (FileNotFoundException)
{
Console.WriteLine("File not found");
}
if (File.Exists("somefile.txt"))
{
string content = File.ReadAllText("somefile.txt");
}
else
{
Console.WriteLine("File not found");
}
int num;
try
{
int num = int.Parse(Console.ReadLine());
}
catch (FormatException)
{
Console.WriteLine("Invalid format");
}
int num;
if (!int.TryParse(Console.ReadLine(), out num))
{
Console.WriteLine("Invalid format");
}
Zinvolle catch
Een try
zonder zinvolle catch
verbergt gewoon de fout, en dat is niet de bedoeling:
try
{
File.Delete("somefile.txt");
}
catch
{
// geen logging, geen actie ondernomen
}
try
{
File.Delete("somefile.txt");
}
catch (IOException e)
{
Console.WriteLine($"Er is een fout opgetreden: {e.Message}")
}
Specifieke exceptietypes
try
{
string content = File.ReadAllText("somefile.txt");
}
catch (Exception ex) // te breed
{
Console.WriteLine("An error occurred");
}
try
{
string content = File.ReadAllText("somefile.txt");
}
catch (IOException) // ok, specifiek genoeg
{
Console.WriteLine("Fout bij het lezen van het bestand");
}
Meerdere catch
-blokken
Overweeg meerdere catch
-blokken, met exceptietypes van specifiek naar algemeen:
string content;
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string filePath = System.IO.Path.Combine(folderPath, "myfile.txt");
try {
content = File.ReadAllText(filePath);
} catch (FileNotFoundException e) { // file not found
lblMessage.Content = $"File {filePath} not found";
} catch (IOException e) { // unable to open for reading
lblMessage.Content = $"Unable to open {filePath}";
} catch (Exception e) { // use general Exception as fallback
lblMessage.Content = $"Unknown error reading {filePath}";
}
// process content
// ...
Merk op dat hoewel de FileNotFoundException
in principe kan vervangen worden door een if (!File.Exists(filePath)) ...
, het gebruik hier te verdedigen is omdat er al een try-catch blok voorkomt.
Beperkte try
-blok
Zet niet meer code in het try
-blok dan strikt nodig:
try {
string content;
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string filePath = System.IO.Path.Combine(folderPath, "myfile.txt");
content = File.ReadAllText(filePath);
// process content
// ...
} catch (FileNotFoundException e) { // file not found
lblMessage.Content = $"File {filePath} not found";
} catch (IOException e) { // unable to open for reading
lblMessage.Content = $"Unable to open {filePath}";
} catch (Exception e) { // use general Exception as fallback
lblMessage.Content = $"Unknown error reading {filePath}";
}
string content;
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string filePath = System.IO.Path.Combine(folderPath, "myfile.txt");
try {
content = File.ReadAllText(filePath);
} catch (FileNotFoundException e) { // file not found
lblMessage.Content = $"File {filePath} not found";
} catch (IOException e) { // unable to open for reading
lblMessage.Content = $"Unable to open {filePath}";
} catch (Exception e) { // use general Exception as fallback
lblMessage.Content = $"Unknown error reading {filePath}";
}
// process content
// ...
Zo laat mogelijk (meestal)
Een vuistregel is dat het beter is de
Finally
Het finally
-blok wordt altijd uitgevoerd op het einde van het try-catch blok, wat er ook gebeurt. Dit verzekert b.v. dat bronnen correct afgesloten worden:
string content;
try
{
reader = new StreamReader("somefile.txt");
content = reader.ReadToEnd();
}
catch (FileNotFoundException)
{
Console.WriteLine($"Bestand niet gevonden");
}
catch (IOException ex)
{
Console.WriteLine($"Fout bij lezen: {ex.Message}");
}
finally
{
reader.Close(); // bestand wordt gesloten, zelfs als een exceptie optreedt
Console.WriteLine("File closed.");
}
Throw
Je kan ook zelf excepties opgooien. Wil je b.v. een methodes al declareren, maar de body pas later uitwerken, dan kan je tijdelijk NotImplementedException
gebruiken
public static User TryLogin(string login, string pw) {
throw new NotImplementedException("Deze feature is nog niet geïmplementeerd");
}
public static User FindUser(int id) {
throw new NotImplementedException("Deze feature is nog niet geïmplementeerd");
}
public static void Delete() {
throw new NotImplementedException("Deze feature is nog niet geïmplementeerd");
}
// ...
In een tweede voorbeeld worden in een methode FindKeywordInFile()
niet alleen excepties opgevangen, maar ook opgegooid, zodat ze hoger in de Main()
weer kunnen opgevangen en afgehandeld worden:
static void Main() {
try {
int lineNumber = FindKeywordInFile("sample.txt", "keyword");
}
catch (FileNotFoundException ex) {
Console.WriteLine($"Fout: {ex.Message}");
}
catch (IOException ex) {
Console.WriteLine($"Fout: {ex.Message}");
}
catch (Exception ex) {
Console.WriteLine($"Onverwachte fout: {ex.Message}");
}
}
static int FindKeywordInFile(string filePath, string keyword) {
if (!File.Exists(filePath)) {
throw new FileNotFoundException($"Bestand '{filePath}' niet gevonden.");
}
if (string.IsNullOrWhiteSpace(keyword)) {
throw new ArgumentException("De zoekterm mag niet null of leeg zijn.");
}
try {
string[] lines = File.ReadAllLines(filePath);
for (int i = 0; i < lines.Length; i++) {
if (lines[i].Contains(keyword)) {
return i + 1; // gevonden op regel i + 1
}
}
return -1; // keyword niet gevonden
}
catch (IOException) {
throw new IOException($"Kan het bestand '{filePath}' niet lezen.");
}
catch (Exception ex) {
throw new Exception("Een onbekende fout is opgetreden.", ex);
}
}
Je kan je de vraag stellen wat voor zin het heeft een exceptie te catchen, en dezelfde exceptie weer te throwen:
static int FindKeywordInFile(string filePath, string keyword) {
...
catch (IOException) {
throw new IOException($"Kan het bestand '{filePath}' niet lezen.");
}
...
}
Op zich kan je dit weglaten; dit werkt evengoed:
static void Main() {
try {
int lineNumber = FindKeywordInFile("sample.txt", "keyword");
}
catch (FileNotFoundException ex) {
Console.WriteLine($"Fout: {ex.Message}");
}
catch (IOException ex) {
Console.WriteLine($"Fout: {ex.Message}");
}
catch (Exception ex) {
Console.WriteLine($"Onverwachte fout: {ex.Message}");
}
}
static int FindKeywordInFile(string filePath, string keyword) {
if (!File.Exists(filePath)) {
throw new FileNotFoundException($"Bestand '{filePath}' niet gevonden.");
}
if (string.IsNullOrWhiteSpace(keyword)) {
throw new ArgumentException("De zoekterm mag niet null of leeg zijn.");
}
string[] lines = File.ReadAllLines(filePath);
for (int i = 0; i < lines.Length; i++) {
if (lines[i].Contains(keyword)) {
return i + 1; // gevonden op regel i + 1
}
}
return -1; // keyword niet gevonden
}
Excepties catchen en weer opgooien is alleen nuttig in volgende situaties:
- aangepaste foutmelding: voeg meer details toe, b.v. om welk bestand het gaat
- aangepaste exceptie type: catch één exceptie type, en throw een ander (b.v. custom) type
- extra acties: je kan bijkomend de fouten loggen, resources vrijgeven enz...
Maar als je niks toevoegt, heeft een exceptie opvangen en weer opgooien geen zin natuurlijk:
...
catch (IOException ex) {
throw ex; // pretty pointless
}
...