1. Wprowadzenie
W Windows (i nie tylko) każdy plik i katalog ma zestaw właściwości (metadanych): pełna ścieżka, nazwa, rozszerzenie, rozmiar, daty utworzenia/modyfikacji/dostępu, atrybuty itd. Wyobraź sobie: plik to nie tylko bajty, ale całe CV, które można odczytać za pomocą FileInfo i DirectoryInfo.
| Właściwość | Opis | Przykład |
|---|---|---|
| Pełna ścieżka | Pełna nazwa pliku/folderu | |
| Nazwa | Nazwa bez ścieżki | |
| Rozszerzenie | .txt, .csv, .jpg itp. | |
| Rozmiar (Bytes) | Rozmiar pliku w bajtach | |
| Data utworzenia | Kiedy plik/folder został utworzony | |
| Data modyfikacji | Kiedy zawartość była ostatnio zmieniana | |
| Atrybuty | Na przykład tylko do odczytu, ukryty itp. | |
| Folder nadrzędny | Folder, w którym znajduje się plik/katalog | |
2. Głębsza praca z właściwościami plików
Szczegółowa analiza znaczników czasu
Właściwości CreationTime, LastWriteTime, LastAccessTime zwracają DateTime i ich zachowanie zależy od systemu plików oraz operacji na pliku.
var fileInfo = new FileInfo("document.txt");
if (fileInfo.Exists)
{
Console.WriteLine($"Utworzono: {fileInfo.CreationTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"Zmodyfikowano: {fileInfo.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"Otwierano: {fileInfo.LastAccessTime:yyyy-MM-dd HH:mm:ss}");
// Różnica między utworzeniem i ostatnią modyfikacją
var age = fileInfo.LastWriteTime - fileInfo.CreationTime;
Console.WriteLine($"Plik był modyfikowany przez: {age.TotalDays:F1} dni");
}
Ciekawe, że przy kopiowaniu pliku data utworzenia zwykle jest aktualizowana na bieżącą, ale data modyfikacji może pozostać z oryginału. To ważne przy backupie i analizie aktywności.
Praca z rozszerzeniami i nazwami plików
Właściwości FullName, Name i Extension wydają się proste, ale bywają niuanse: brak rozszerzenia, złożone rozszerzenia typu .tar.gz oraz ukryte pliki zaczynające się od kropki.
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($"Pełna nazwa: {file.FullName}");
Console.WriteLine($"Nazwa: {file.Name}");
Console.WriteLine($"Rozszerzenie: '{file.Extension}'");
// Nazwa bez rozszerzenia
string nameWithoutExtension = Path.GetFileNameWithoutExtension(file.Name);
Console.WriteLine($"Nazwa bez rozszerzenia: {nameWithoutExtension}");
Console.WriteLine("---");
}
Rozmiar plików i formatowanie
Właściwość Length zwraca rozmiar w bajtach; dla użytkownika wygodniej widzieć KB/MB/GB. Funkcja pomocnicza:
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]}";
}
// Użycie
var file = new FileInfo("bigfile.zip");
if (file.Exists)
{
Console.WriteLine($"Rozmiar pliku: {FormatFileSize(file.Length)}");
}
3. Rozszerzona praca z atrybutami plików
Atrybuty są reprezentowane przez enumerację FileAttributes (zestaw flag bitowych), więc plik może mieć kilka atrybutów jednocześnie. Wygodnie sprawdzać przez HasFlag.
var fileInfo = new FileInfo("important.txt");
Console.WriteLine($"Atrybuty pliku: {fileInfo.Attributes}");
// Sprawdzamy konkretne atrybuty
if (fileInfo.Attributes.HasFlag(FileAttributes.Hidden))
{
Console.WriteLine("Plik jest ukryty!");
}
if (fileInfo.Attributes.HasFlag(FileAttributes.ReadOnly))
{
Console.WriteLine("Plik jest zabezpieczony przed zapisem!");
}
if (fileInfo.Attributes.HasFlag(FileAttributes.System))
{
Console.WriteLine("To plik systemowy!");
}
Atrybuty można zmieniać programowo:
// Ustawić plik jako ukryty
fileInfo.Attributes |= FileAttributes.Hidden;
// Usunąć atrybut "tylko do odczytu"
fileInfo.Attributes &= ~FileAttributes.ReadOnly;
// Ustawić kilka atrybutów naraz
fileInfo.Attributes = FileAttributes.ReadOnly | FileAttributes.Hidden;
4. Zaawansowana praca z katalogami
Wyszukiwanie plików po wzorcach
Metody GetFiles() i GetDirectories() przyjmują wzorce i pomagają filtrować zawartość.
var dir = new DirectoryInfo(@"C:\Projects");
if (dir.Exists)
{
// Znaleźć wszystkie pliki tekstowe
var textFiles = dir.GetFiles("*.txt");
Console.WriteLine($"Znaleziono plików tekstowych: {textFiles.Length}");
// Znaleźć wszystkie pliki zaczynające się od "temp"
var tempFiles = dir.GetFiles("temp*");
// Znaleźć wszystkie pliki obrazów
var imageExtensions = new[] { "*.jpg", "*.png", "*.gif", "*.bmp" };
var allImages = imageExtensions.SelectMany(ext => dir.GetFiles(ext)).ToArray();
Console.WriteLine($"Znaleziono obrazów: {allImages.Length}");
}
Rekurencyjne wyszukiwanie w podkatalogach
Do przejścia przez wszystkie podfoldery użyj SearchOption.AllDirectories.
var dir = new DirectoryInfo(@"C:\Development");
// Znaleźć wszystkie pliki C# we wszystkich podfolderach
var csharpFiles = dir.GetFiles("*.cs", SearchOption.AllDirectories);
Console.WriteLine($"Łącznie znaleziono plików .cs: {csharpFiles.Length}");
// Pokaż pierwsze 10 plików z ich ścieżkami
foreach (var file in csharpFiles.Take(10))
{
Console.WriteLine($"{file.FullName} ({FormatFileSize(file.Length)})");
}
Analiza zawartości katalogu
Przykład podsumowania: liczba plików/folderów, łączny rozmiar, rozkład po rozszerzeniach i top-5 największych plików.
static void AnalyzeDirectory(DirectoryInfo dir)
{
if (!dir.Exists)
{
Console.WriteLine("Katalog nie istnieje!");
return;
}
var files = dir.GetFiles();
var subdirs = dir.GetDirectories();
Console.WriteLine($"Analiza katalogu: {dir.FullName}");
Console.WriteLine($"Plików: {files.Length}, Podkatalogów: {subdirs.Length}");
if (files.Length == 0)
{
Console.WriteLine("Brak plików.");
return;
}
long totalSize = files.Sum(f => f.Length);
Console.WriteLine($"Łączny rozmiar plików: {FormatFileSize(totalSize)}");
// Grupowanie po rozszerzeniach
var byExtension = files.GroupBy(f => f.Extension.ToLower())
.OrderByDescending(g => g.Sum(f => f.Length));
Console.WriteLine("\nRozkład według typów plików:");
foreach (var group in byExtension)
{
string ext = string.IsNullOrEmpty(group.Key) ? "(bez rozszerzenia)" : group.Key;
long groupSize = group.Sum(f => f.Length);
Console.WriteLine($" {ext}: {group.Count()} plików, {FormatFileSize(groupSize)}");
}
// Top-5 największych plików
var largestFiles = files.OrderByDescending(f => f.Length).Take(5);
Console.WriteLine("\nNajwiększe pliki:");
foreach (var file in largestFiles)
{
Console.WriteLine($" {file.Name}: {FormatFileSize(file.Length)}");
}
}
5. Optymalizowane liczenie rozmiaru katalogu
Dla dużych folderów zamiast GetFiles() używaj leniwych enumeratorów EnumerateFiles()/EnumerateDirectories(), obsługuj wyjątki i opcjonalnie pokazuj postęp.
static long GetDirectorySizeAdvanced(DirectoryInfo dir, bool showProgress = false)
{
long totalSize = 0;
int fileCount = 0;
var inaccessibleDirs = new List<string>();
try
{
// Używamy EnumerateFiles dla dużych katalogów (leniwe ładowanie)
foreach (var file in dir.EnumerateFiles())
{
try
{
totalSize += file.Length;
fileCount++;
if (showProgress && fileCount % 1000 == 0)
{
Console.WriteLine($"Przetworzono plików: {fileCount}, rozmiar: {FormatFileSize(totalSize)}");
}
}
catch (UnauthorizedAccessException)
{
// Plik niedostępny, pomijamy
}
catch (IOException)
{
// Problemy z odczytem pliku, pomijamy
}
}
// Rekurencyjnie przetwarzamy podkatalogi
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($"Niedostępnych katalogów: {inaccessibleDirs.Count}");
}
return totalSize;
}
// Użycie
var targetDir = new DirectoryInfo(@"C:\Users");
Console.WriteLine("Zaczynamy liczenie rozmiaru katalogu...");
long size = GetDirectorySizeAdvanced(targetDir, showProgress: true);
Console.WriteLine($"Łączny rozmiar: {FormatFileSize(size)}");
6. Praktyczne zastosowania metadanych
Wyszukiwanie plików po dacie
Szukamy plików zmodyfikowanych w zadanym okresie (wygodne do sprzątania, analizy lub audytu).
static void FindFilesByDate(DirectoryInfo dir, DateTime fromDate, DateTime toDate)
{
Console.WriteLine($"Wyszukiwanie plików od {fromDate:yyyy-MM-dd} do {toDate:yyyy-MM-dd}");
var matchingFiles = dir.GetFiles("*", SearchOption.AllDirectories)
.Where(f => f.LastWriteTime >= fromDate && f.LastWriteTime <= toDate)
.OrderByDescending(f => f.LastWriteTime);
Console.WriteLine($"Znaleziono plików: {matchingFiles.Count()}");
foreach (var file in matchingFiles.Take(20))
{
Console.WriteLine($"{file.LastWriteTime:yyyy-MM-dd HH:mm} - {file.Name} ({FormatFileSize(file.Length)})");
}
}
// Przykład: znaleźć wszystkie pliki zmodyfikowane w ciągu ostatniego tygodnia
var dir = new DirectoryInfo(@"C:\Documents");
var weekAgo = DateTime.Now.AddDays(-7);
FindFilesByDate(dir, weekAgo, DateTime.Now);
Wyszukiwanie duplikujących się plików
Szybka metoda — pogrupować po rozmiarze. Dla dokładności można dodać porównanie hashy zawartości.
static void FindPotentialDuplicates(DirectoryInfo dir)
{
Console.WriteLine($"Wyszukiwanie potencjalnych duplikatów w {dir.FullName}");
var files = dir.GetFiles("*", SearchOption.AllDirectories)
.Where(f => f.Length > 0) // Wykluczamy puste pliki
.GroupBy(f => f.Length)
.Where(g => g.Count() > 1) // Tylko grupy z kilkoma plikami
.OrderByDescending(g => g.Key); // Sortujemy po rozmiarze
foreach (var sizeGroup in files.Take(10))
{
Console.WriteLine($"\nPliki o rozmiarze {FormatFileSize(sizeGroup.Key)} ({sizeGroup.Count()} sztuk):");
foreach (var file in sizeGroup)
{
Console.WriteLine($" {file.FullName}");
Console.WriteLine($" Zmodyfikowano: {file.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
}
}
}
Monitoring zmian w katalogu
Pokazujemy pliki zmodyfikowane w ciągu ostatnich N minut.
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($"Pliki zmodyfikowane w ciągu ostatnich {minutesBack} minut:");
if (!recentFiles.Any())
{
Console.WriteLine("Brak zmian.");
return;
}
foreach (var file in recentFiles)
{
var minutesAgo = (DateTime.Now - file.LastWriteTime).TotalMinutes;
Console.WriteLine($"{file.Name} - {minutesAgo:F0} min. temu ({FormatFileSize(file.Length)})");
}
}
7. Praca z katalogami nadrzędnymi
Właściwość Directory w FileInfo i Parent w DirectoryInfo pozwalają wspinać się po hierarchii.
var file = new FileInfo(@"C:\Projects\MyApp\src\Program.cs");
Console.WriteLine($"Plik: {file.Name}");
Console.WriteLine($"Folder: {file.Directory.Name}");
Console.WriteLine($"Folder nadrzędny: {file.Directory.Parent.Name}");
Console.WriteLine($"Główny folder projektu: {file.Directory.Parent.Parent.Name}");
// Można wspinać się po hierarchii aż do root
var currentDir = file.Directory;
while (currentDir.Parent != null)
{
Console.WriteLine($"Poziom: {currentDir.Name}");
currentDir = currentDir.Parent;
}
Console.WriteLine($"Root: {currentDir.Name}");
8. Pułapki i typowe błędy
1. Cache'owanie. Obiekty FileInfo i DirectoryInfo cache'ują wartości. Jeśli obiekt zmienił się po utworzeniu, dane mogą być nieaktualne. Użyj Refresh() żeby odświeżyć.
var file = new FileInfo("test.txt");
file.Refresh(); // Odświeżenie metadanych
2. Wyjątki dostępu. Niektóre pliki i foldery są niedostępne: obsługuj UnauthorizedAccessException i inne błędy dostępu.
try
{
var files = new DirectoryInfo(path).GetFiles();
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Brak dostępu");
}
3. Znaczniki czasu. Przy kopiowaniu CreationTime może się zmienić, a LastWriteTime — pozostać. To wpływa na raporty i algorytmy synchronizacji.
File.Copy("a.txt", "b.txt");
4. Wydajność. GetFiles() ładuje wszystko od razu i może spowalniać przy dużych katalogach. Preferuj EnumerateFiles() do leniwego enumerowania.
GO TO FULL VERSION