1. Einführung
In Windows (und nicht nur dort) hat jede Datei und jedes Verzeichnis eine Menge Eigenschaften (Metadaten): vollständiger Pfad, Name, Erweiterung, Größe, Erstell-/Änderungs-/Zugriffszeiten, Attribute usw. Stell dir vor: eine Datei ist nicht nur Bytes, sondern ein ganzes Formular, das man mit FileInfo und DirectoryInfo auslesen kann.
| Eigenschaft | Beschreibung | Beispiel |
|---|---|---|
| Vollständiger Pfad | Vollständiger Name der Datei/des Ordners | |
| Name | Name ohne Pfad | |
| Erweiterung | .txt, .csv, .jpg usw. | |
| Größe (Bytes) | Dateigröße in Bytes | |
| Erstellungsdatum | Wann die Datei/der Ordner erstellt wurde | |
| Änderungsdatum | Wann der Inhalt zuletzt geändert wurde | |
| Attribute | Z.B. ReadOnly, Hidden usw. | |
| Übergeordneter Ordner | Der Ordner, in dem sich die Datei/das Verzeichnis befindet | |
2. Tiefergehende Arbeit mit Dateieigenschaften
Detaillierte Analyse von Zeitstempeln
Die Eigenschaften CreationTime, LastWriteTime und LastAccessTime liefern ein DateTime und ihr Verhalten hängt vom Dateisystem und von Dateioperationen ab.
var fileInfo = new FileInfo("document.txt");
if (fileInfo.Exists)
{
Console.WriteLine($"Erstellt: {fileInfo.CreationTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"Geändert: {fileInfo.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"Zugegriffen: {fileInfo.LastAccessTime:yyyy-MM-dd HH:mm:ss}");
// Differenz zwischen Erstellung und letzter Änderung
var age = fileInfo.LastWriteTime - fileInfo.CreationTime;
Console.WriteLine($"Die Datei wurde über: {age.TotalDays:F1} Tage geändert");
}
Interessant ist, dass beim Kopieren einer Datei das Erstellungsdatum oft auf das aktuelle Datum gesetzt wird, während das Änderungsdatum vom Original erhalten bleiben kann. Das ist wichtig für Backups und Aktivitätsanalyse.
Arbeiten mit Erweiterungen und Dateinamen
Die Eigenschaften FullName, Name und Extension wirken simpel, aber es gibt Feinheiten: fehlende Erweiterung, zusammengesetzte Erweiterungen wie .tar.gz und versteckte Dateien, die mit einem Punkt beginnen.
var files = new[]
{
new FileInfo("document.txt"),
new FileInfo("archive.tar.gz"),
new FileInfo("README"),
new FileInfo(".gitignore")
};
foreach (var file in files)
{
Console.WriteLine($"Vollständiger Name: {file.FullName}");
Console.WriteLine($"Name: {file.Name}");
Console.WriteLine($"Erweiterung: '{file.Extension}'");
// Name ohne Erweiterung
string nameWithoutExtension = Path.GetFileNameWithoutExtension(file.Name);
Console.WriteLine($"Name ohne Erweiterung: {nameWithoutExtension}");
Console.WriteLine("---");
}
Dateigrößen und Formatierung
Die Eigenschaft Length liefert die Größe in Bytes; für Nutzer ist es praktischer, KB/MB/GB zu sehen. Hilfsfunktion:
static string FormatFileSize(long bytes)
{
string[] suffixes = { "B", "KB", "MB", "GB", "TB" };
int counter = 0;
decimal number = bytes;
while (Math.Round(number / 1024) >= 1)
{
number /= 1024;
counter++;
}
return $"{number:N1} {suffixes[counter]}";
}
// Verwendung
var file = new FileInfo("bigfile.zip");
if (file.Exists)
{
Console.WriteLine($"Dateigröße: {FormatFileSize(file.Length)}");
}
3. Erweiterte Arbeit mit Datei-Attributen
Attribute werden durch das Enum FileAttributes dargestellt (Bitflags), daher kann eine Datei mehrere Attribute gleichzeitig haben. Prüfen geht bequem mit HasFlag.
var fileInfo = new FileInfo("important.txt");
Console.WriteLine($"Dateiattribute: {fileInfo.Attributes}");
// Konkrete Attribute prüfen
if (fileInfo.Attributes.HasFlag(FileAttributes.Hidden))
{
Console.WriteLine("Die Datei ist versteckt!");
}
if (fileInfo.Attributes.HasFlag(FileAttributes.ReadOnly))
{
Console.WriteLine("Die Datei ist schreibgeschützt!");
}
if (fileInfo.Attributes.HasFlag(FileAttributes.System))
{
Console.WriteLine("Das ist eine Systemdatei!");
}
Attribute lassen sich programmatisch ändern:
// Datei verstecken
fileInfo.Attributes |= FileAttributes.Hidden;
// Attribut "ReadOnly" entfernen
fileInfo.Attributes &= ~FileAttributes.ReadOnly;
// Mehrere Attribute gleichzeitig setzen
fileInfo.Attributes = FileAttributes.ReadOnly | FileAttributes.Hidden;
4. Fortgeschrittene Arbeit mit Verzeichnissen
Dateisuche mit Mustern
Die Methoden GetFiles() und GetDirectories() akzeptieren Muster und helfen beim Filtern des Inhalts.
var dir = new DirectoryInfo(@"C:\Projects");
if (dir.Exists)
{
// Alle Textdateien finden
var textFiles = dir.GetFiles("*.txt");
Console.WriteLine($"Gefundene Textdateien: {textFiles.Length}");
// Alle Dateien finden, die mit "temp" beginnen
var tempFiles = dir.GetFiles("temp*");
// Alle Bilddateien finden
var imageExtensions = new[] { "*.jpg", "*.png", "*.gif", "*.bmp" };
var allImages = imageExtensions.SelectMany(ext => dir.GetFiles(ext)).ToArray();
Console.WriteLine($"Gefundene Bilder: {allImages.Length}");
}
Rekursives Suchen in Unterordnern
Für das Durchlaufen aller Unterordner verwendet man SearchOption.AllDirectories.
var dir = new DirectoryInfo(@"C:\Development");
// Alle C#-Dateien in allen Unterordnern finden
var csharpFiles = dir.GetFiles("*.cs", SearchOption.AllDirectories);
Console.WriteLine($"Insgesamt gefundene .cs Dateien: {csharpFiles.Length}");
// Die ersten 10 Dateien mit ihren Pfaden anzeigen
foreach (var file in csharpFiles.Take(10))
{
Console.WriteLine($"{file.FullName} ({FormatFileSize(file.Length)})");
}
Analyse des Verzeichnisinhalts
Beispiel für eine Zusammenfassung: Anzahl Dateien/Ordner, Gesamtgröße, Verteilung nach Erweiterungen und Top-5 der größten Dateien.
static void AnalyzeDirectory(DirectoryInfo dir)
{
if (!dir.Exists)
{
Console.WriteLine("Das Verzeichnis existiert nicht!");
return;
}
var files = dir.GetFiles();
var subdirs = dir.GetDirectories();
Console.WriteLine($"Analyse des Verzeichnisses: {dir.FullName}");
Console.WriteLine($"Dateien: {files.Length}, Unterverzeichnisse: {subdirs.Length}");
if (files.Length == 0)
{
Console.WriteLine("Keine Dateien gefunden.");
return;
}
long totalSize = files.Sum(f => f.Length);
Console.WriteLine($"Gesamtgröße der Dateien: {FormatFileSize(totalSize)}");
// Gruppierung nach Erweiterungen
var byExtension = files.GroupBy(f => f.Extension.ToLower())
.OrderByDescending(g => g.Sum(f => f.Length));
Console.WriteLine("\nVerteilung nach Dateitypen:");
foreach (var group in byExtension)
{
string ext = string.IsNullOrEmpty(group.Key) ? "(ohne Erweiterung)" : group.Key;
long groupSize = group.Sum(f => f.Length);
Console.WriteLine($" {ext}: {group.Count()} Dateien, {FormatFileSize(groupSize)}");
}
// Top-5 der größten Dateien
var largestFiles = files.OrderByDescending(f => f.Length).Take(5);
Console.WriteLine("\nDie größten Dateien:");
foreach (var file in largestFiles)
{
Console.WriteLine($" {file.Name}: {FormatFileSize(file.Length)}");
}
}
5. Optimierte Berechnung der Verzeichnisgröße
Für große Ordner sollte man statt GetFiles() die lazy-Enumerators EnumerateFiles()/EnumerateDirectories() verwenden, Ausnahmen behandeln und eventuell Fortschritt anzeigen.
static long GetDirectorySizeAdvanced(DirectoryInfo dir, bool showProgress = false)
{
long totalSize = 0;
int fileCount = 0;
var inaccessibleDirs = new List<string>();
try
{
// EnumerateFiles für große Verzeichnisse verwenden (lazy loading)
foreach (var file in dir.EnumerateFiles())
{
try
{
totalSize += file.Length;
fileCount++;
if (showProgress && fileCount % 1000 == 0)
{
Console.WriteLine($"Verarbeitete Dateien: {fileCount}, Größe: {FormatFileSize(totalSize)}");
}
}
catch (UnauthorizedAccessException)
{
// Datei nicht zugänglich, überspringen
}
catch (IOException)
{
// Leseproblem, überspringen
}
}
// Unterverzeichnisse rekursiv verarbeiten
foreach (var subdir in dir.EnumerateDirectories())
{
try
{
totalSize += GetDirectorySizeAdvanced(subdir, showProgress);
}
catch (UnauthorizedAccessException)
{
inaccessibleDirs.Add(subdir.FullName);
}
}
}
catch (UnauthorizedAccessException)
{
inaccessibleDirs.Add(dir.FullName);
}
if (inaccessibleDirs.Any() && showProgress)
{
Console.WriteLine($"Nicht zugängliche Verzeichnisse: {inaccessibleDirs.Count}");
}
return totalSize;
}
// Verwendung
var targetDir = new DirectoryInfo(@"C:\Users");
Console.WriteLine("Beginne mit der Berechnung der Verzeichnisgröße...");
long size = GetDirectorySizeAdvanced(targetDir, showProgress: true);
Console.WriteLine($"Gesamtgröße: {FormatFileSize(size)}");
6. Praktische Anwendungen von Metadaten
Dateien nach Datum suchen
Dateien finden, die in einem bestimmten Zeitraum geändert wurden (praktisch für Bereinigung, Analyse oder Audit).
static void FindFilesByDate(DirectoryInfo dir, DateTime fromDate, DateTime toDate)
{
Console.WriteLine($"Suche Dateien von {fromDate:yyyy-MM-dd} bis {toDate:yyyy-MM-dd}");
var matchingFiles = dir.GetFiles("*", SearchOption.AllDirectories)
.Where(f => f.LastWriteTime >= fromDate && f.LastWriteTime <= toDate)
.OrderByDescending(f => f.LastWriteTime);
Console.WriteLine($"Gefundene Dateien: {matchingFiles.Count()}");
foreach (var file in matchingFiles.Take(20))
{
Console.WriteLine($"{file.LastWriteTime:yyyy-MM-dd HH:mm} - {file.Name} ({FormatFileSize(file.Length)})");
}
}
// Beispiel: alle Dateien finden, die in der letzten Woche geändert wurden
var dir = new DirectoryInfo(@"C:\Documents");
var weekAgo = DateTime.Now.AddDays(-7);
FindFilesByDate(dir, weekAgo, DateTime.Now);
Doppelte Dateien finden
Ein schneller Ansatz ist Gruppierung nach Größe. Für Genauigkeit kann man zusätzlich Hash-Vergleiche des Inhalts durchführen.
static void FindPotentialDuplicates(DirectoryInfo dir)
{
Console.WriteLine($"Suche nach potentiellen Duplikaten in {dir.FullName}");
var files = dir.GetFiles("*", SearchOption.AllDirectories)
.Where(f => f.Length > 0) // Leere Dateien ausschließen
.GroupBy(f => f.Length)
.Where(g => g.Count() > 1) // Nur Gruppen mit mehreren Dateien
.OrderByDescending(g => g.Key); // Nach Größe sortieren
foreach (var sizeGroup in files.Take(10))
{
Console.WriteLine($"\nDateien mit Größe {FormatFileSize(sizeGroup.Key)} ({sizeGroup.Count()} Stück):");
foreach (var file in sizeGroup)
{
Console.WriteLine($" {file.FullName}");
Console.WriteLine($" Geändert: {file.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
}
}
}
Überwachung von Änderungen in einem Verzeichnis
Zeigt Dateien, die in den letzten N Minuten geändert wurden.
static void MonitorRecentChanges(DirectoryInfo dir, int minutesBack = 60)
{
var cutoffTime = DateTime.Now.AddMinutes(-minutesBack);
var recentFiles = dir.GetFiles("*", SearchOption.AllDirectories)
.Where(f => f.LastWriteTime > cutoffTime)
.OrderByDescending(f => f.LastWriteTime);
Console.WriteLine($"Dateien, die in den letzten {minutesBack} Minuten geändert wurden:");
if (!recentFiles.Any())
{
Console.WriteLine("Keine Änderungen gefunden.");
return;
}
foreach (var file in recentFiles)
{
var minutesAgo = (DateTime.Now - file.LastWriteTime).TotalMinutes;
Console.WriteLine($"{file.Name} - {minutesAgo:F0} Min. zuvor ({FormatFileSize(file.Length)})");
}
}
7. Arbeit mit übergeordneten Verzeichnissen
Die Eigenschaft Directory bei FileInfo und Parent bei DirectoryInfo erlauben das Aufsteigen in der Hierarchie.
var file = new FileInfo(@"C:\Projects\MyApp\src\Program.cs");
Console.WriteLine($"Datei: {file.Name}");
Console.WriteLine($"Ordner: {file.Directory.Name}");
Console.WriteLine($"Übergeordneter Ordner: {file.Directory.Parent.Name}");
Console.WriteLine($"Projekt-Root-Ordner: {file.Directory.Parent.Parent.Name}");
// Man kann bis zur Wurzel hochsteigen
var currentDir = file.Directory;
while (currentDir.Parent != null)
{
Console.WriteLine($"Ebene: {currentDir.Name}");
currentDir = currentDir.Parent;
}
Console.WriteLine($"Root: {currentDir.Name}");
8. Fallen und typische Fehler
1. Caching. Die Objekte FileInfo und DirectoryInfo cachen Werte. Wenn sich das Objekt nach der Erstellung geändert hat, können die Daten veraltet sein. Verwende Refresh(), um die Daten zu aktualisieren.
var file = new FileInfo("test.txt");
file.Refresh(); // Metadaten aktualisieren
2. Zugriffsausnahmen. Manche Dateien und Ordner sind nicht zugänglich: behandle UnauthorizedAccessException und andere Zugriffsfehler.
try
{
var files = new DirectoryInfo(path).GetFiles();
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Kein Zugriff");
}
3. Zeitstempel. Beim Kopieren kann CreationTime verändert werden, während LastWriteTime erhalten bleibt. Das wirkt sich auf Berichte und Synchronisationsalgorithmen aus.
File.Copy("a.txt", "b.txt");
4. Performance. GetFiles() lädt alles auf einmal und kann bei großen Ordnern langsam werden. Bevorzuge EnumerateFiles() für lazy-Aufzählung.
GO TO FULL VERSION