Der Namespace System.IO hat alles für die Arbeit mit Dateien. Für die meisten Aufgaben reicht ein Aufruf der statischen Klasse File: Sie öffnet die Datei, liest oder schreibt sie und schließt sie. Für große Dateien oder feine Kontrolle nimm StreamReader und StreamWriter. Füge jedem Beispiel unten using System.IO; hinzu.
Eine ganze Datei schreiben und lesen
File.WriteAllText erzeugt eine Datei (oder überschreibt eine vorhandene) mit dem Text, den du übergibst. File.ReadAllText gibt die ganze Datei als einen String zurück. File.AppendAllText hängt ans Ende an:
Ausgabe:
True
Buy coffee
Call the dentist
Renew passport
43 characters
Start over
False
Ein relativer Pfad wie "notes.txt" wird gegen das aktuelle Verzeichnis des Prozesses aufgelöst, und das ist nicht immer der Ordner, in dem dein Programm liegt (ein Dienst, eine geplante Aufgabe oder ein Test-Runner kann woanders starten). Für Dateien, die mit deiner Anwendung ausgeliefert werden, baue den Pfad aus AppContext.BaseDirectory.
WriteAllText überschreibt ohne Rückfrage. Wenn es wichtig ist, den alten Inhalt nicht zu verlieren, prüfe zuerst File.Exists oder schreibe in eine temporäre Datei und verschiebe sie dann mit File.Move an ihren Platz.
Zeilen: WriteAllLines, ReadAllLines und ReadLines
Dateien mit Datensätzen, einer pro Zeile, sind so verbreitet, dass sie eigene Methoden haben. WriteAllLines schreibt jeden String gefolgt von einem Zeilenumbruch; ReadAllLines gibt ein string[] zurück:
Ausgabe:
4 lines, header: date,product,amount
Total: 45.60
2026-03-03,Stapler,7.00
Der Unterschied zwischen den beiden Lesemethoden zählt bei der Größe. ReadAllLines liest die ganze Datei in den Speicher, bevor es zurückkehrt. ReadLines gibt ein verzögertes IEnumerable<string> zurück, das beim Durchlaufen liest, ein foreach über ein Log von 5 GB braucht also ein paar Kilobyte. Mit LINQ hört ReadLines(...).Where(...).Take(10) auf zu lesen, sobald es zehn Treffer hat.
CSV mit Split(',') zu parsen funktioniert bei einfachen Dateien, die du selbst erzeugt hast. CSV aus der Praxis hat Felder in Anführungszeichen, die Kommas enthalten; nimm dafür eine Bibliothek wie CsvHelper.
Beachte das InvariantCulture in decimal.Parse: Ohne es liest das Parsen von "4.50" auf einem auf Deutsch oder Portugiesisch eingestellten Rechner den Punkt als Tausendertrennzeichen.
StreamWriter und StreamReader
Die File-Methoden öffnen und schließen die Datei bei jedem Aufruf. Um über die Zeit viele Teile zu schreiben oder eine Datei zu lesen, die ohne LINQ zu groß für den Speicher ist, öffne einmal einen Stream. Umschließe ihn immer mit using, damit er auch bei einer Exception geschlossen wird; ein nicht geschlossener Writer leert seinen Puffer vielleicht nie, und die Datei bleibt leer oder abgeschnitten.
Ausgabe:
2: WARN disk 85% full
4: ERROR connection refused
ReadLine gibt am Ende der Datei null zurück, und das beendet die while-Schleife. Ein StreamWriter puffert seine Ausgabe und schreibt sie auf die Festplatte, wenn der Puffer voll ist, wenn du Flush() aufrufst und wenn er freigegeben wird; der using-Block garantiert das Letzte.
Seit C# 8 gibt eine using-Deklaration den Stream am Ende des umgebenden Blocks frei, ohne die zusätzlichen geschweiften Klammern:
using var writer = new StreamWriter(path); // C# 8: disposed when the method returns
writer.WriteLine("INFO server started");
Pfade und Verzeichnisse
Baue Pfade mit Path.Combine statt mit String-Verkettung. Es setzt das richtige Trennzeichen für das Betriebssystem ein (\ unter Windows, / unter Linux und macOS) und verdoppelt es nicht. Die Klasse Path zerlegt Pfade auch, und Directory erzeugt und listet Ordner:
Ausgabe:
february.txt, january.txt, march.txt
march.txt
march
.txt
march.pdf
False
Eine Falle: Ist ein späteres Argument von Path.Combine absolut (es beginnt mit / oder \ oder unter Windows mit einem Laufwerksbuchstaben), wird alles davor verworfen, Path.Combine("reports", "/2026") ist also /2026. Übergib relative Teile oder nimm Path.Join (ab .NET Core 3.0), das nie etwas verwirft.
Directory.GetFiles gibt Dateien in der Reihenfolge zurück, in der das Dateisystem sie liefert, und die unterscheidet sich zwischen Windows und Linux, sortiere das Ergebnis also, wenn die Reihenfolge zählt. Directory.EnumerateFiles ist die verzögerte Version, dieselbe Beziehung wie zwischen ReadLines und ReadAllLines. Übergib SearchOption.AllDirectories, um Unterordner einzuschließen.
Fehler behandeln
Dateioperationen scheitern aus Gründen außerhalb deines Programms: Die Datei fehlt, der Ordner existiert nicht, ein anderer Prozess hat sie offen, die Festplatte ist voll, die Berechtigungen stimmen nicht. Jeder Fall hat seinen eigenen Exception-Typ. FileNotFoundException und DirectoryNotFoundException leiten von IOException ab, fang sie also davor ab; UnauthorizedAccessException tut das nicht und braucht daher ein eigenes catch:
Ausgabe:
missing file, using defaults
missing folder, using defaults
theme=dark
File.Exists vor dem Lesen wirkt einfacher, macht das try aber nicht überflüssig: Die Datei kann in dem Moment zwischen Prüfung und Lesen verschwinden oder von einem anderen Programm gesperrt werden. Nimm Exists, um zu entscheiden, was zu tun ist, und catch, um zu überstehen, was tatsächlich passiert.
Kodierung
File.WriteAllText, WriteAllLines und StreamWriter schreiben standardmäßig UTF-8 ohne Byte Order Mark, und die Lesemethoden erkennen UTF-8, UTF-16 und UTF-32 an einem Byte Order Mark, falls einer vorhanden ist. Übergib ein Encoding, wenn eine Datei ein bestimmtes Format haben muss:
Ausgabe:
plain.txt 16 bytes, reads back unchanged: True
bom.txt 19 bytes, reads back unchanged: True
utf16.txt 30 bytes, reads back unchanged: True
é und ã belegen in UTF-8 jeweils zwei Byte, deshalb brauchen 14 Zeichen 16 Byte, und alle drei Dateien werden korrekt zurückgelesen, weil der Reader den Byte Order Mark erkennt. Encoding.UTF8 sieht aus wie der Standard, fügt aber einen Byte Order Mark hinzu, den manche Tools (ältere CSV-Importer, Shell-Skripte) als Müll am Anfang der ersten Zeile anzeigen; nimm new UTF8Encoding(false) für explizites UTF-8 ohne. Eine Datei in der falschen Kodierung zu lesen wirft nicht: Es erzeugt Ersatzzeichen, finde also heraus, welche Kodierung eine Datei verwendet, die du nicht selbst erzeugt hast.
Asynchrone Datei-I/O
In Web-Apps und UI-Code verschwendet es einen Thread, ihn bei Festplatten-I/O zu blockieren. .NET Core 2.0 und neuer haben async-Versionen der File-Methoden:
await File.WriteAllTextAsync("notes.txt", text);
string content = await File.ReadAllTextAsync("notes.txt");
string[] lines = await File.ReadAllLinesAsync("sales.csv");
StreamReader.ReadLineAsync und StreamWriter.WriteLineAsync funktionieren bei Streams genauso.
Häufige Fehler
- Einen Stream nicht freigeben. Ohne
usingerreicht gepufferter Text vielleicht nie die Festplatte, und die Datei bleibt gesperrt, bis der Garbage Collector läuft. - Pfade mit
+ "\\" +bauen. Das bricht unter Linux und macOS. NimmPath.Combine. - Riesige Dateien mit
ReadAllTextoderReadAllLinesladen. Streame sie mitReadLinesoder einemStreamReader. - Sich auf das aktuelle Verzeichnis verlassen. Es hängt davon ab, wie das Programm gestartet wurde. Nimm absolute Pfade oder
AppContext.BaseDirectory. - Zahlen aus Dateien mit der Kultur des Rechners parsen. Übergib für Datendateien
CultureInfo.InvariantCulture. - Erwarten, dass
File.ExistsExceptions verhindert. Die Datei kann sich zwischen Prüfung und Verwendung ändern.
Häufig gestellte Fragen
Wie schreibe ich in C# Text in eine Datei?
File.WriteAllText("notes.txt", text); erzeugt die Datei oder überschreibt sie, wenn sie existiert, schreibt den String als UTF-8 und schließt sie. Nimm File.WriteAllLines(path, lines) für eine Sammlung von Zeilen und File.AppendAllText(path, text), um ans Ende anzuhängen, statt zu ersetzen. Alle drei liegen in System.IO.
Wie lese ich in C# eine Textdatei?
string text = File.ReadAllText("notes.txt"); liest die ganze Datei in einen String, und string[] lines = File.ReadAllLines(path); teilt sie in Zeilen auf. Für große Dateien nimm File.ReadLines(path) in einem foreach, das eine Zeile nach der anderen liest, statt alles zu laden, oder einen StreamReader.
Wie hänge ich in C# etwas an eine Datei an?
File.AppendAllText(path, text) fügt Text ans Ende der Datei an und erzeugt die Datei, falls sie nicht existiert; File.AppendAllLines(path, lines) tut dasselbe für Zeilen. Mit einem Stream öffnest du ihn im Anhängemodus: new StreamWriter(path, append: true). Denk daran, beim Anhängen von Text Environment.NewLine oder \n selbst hinzuzufügen.
Wie lese ich in C# eine Datei Zeile für Zeile?
foreach (string line in File.ReadLines(path)) { ... } streamt die Datei Zeile für Zeile, der Speicherverbrauch bleibt also selbst bei Logs von mehreren Gigabyte flach. Die ältere Entsprechung ist ein StreamReader in einem using-Block mit while ((line = reader.ReadLine()) != null). Vermeide ReadAllLines bei großen Dateien: Es lädt zuerst jede Zeile in ein Array.
Wie prüfe ich in C#, ob eine Datei existiert?
File.Exists(path) gibt true zurück, wenn die Datei existiert und das Programm sie sehen darf, und Directory.Exists(path) tut dasselbe für Ordner. Verlass dich nicht darauf, um Exceptions zu vermeiden: Die Datei kann zwischen Prüfung und Lesen gelöscht oder gesperrt werden, behandle also trotzdem FileNotFoundException und IOException um die eigentliche Dateioperation.