CodeGym /Kursy /C# SELF /Uzyskiwanie informacji o plikach i katalogach

Uzyskiwanie informacji o plikach i katalogach

C# SELF
Poziom 39, Lekcja 2
Dostępny

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
C:\data\myfile.txt
Nazwa Nazwa bez ścieżki
myfile.txt
Rozszerzenie .txt, .csv, .jpg itp.
.txt
Rozmiar (Bytes) Rozmiar pliku w bajtach
4096
Data utworzenia Kiedy plik/folder został utworzony
2024-04-16 19:30:10
Data modyfikacji Kiedy zawartość była ostatnio zmieniana
2024-05-01 18:14:02
Atrybuty Na przykład tylko do odczytu, ukryty itp.
ReadOnly, Hidden
Folder nadrzędny Folder, w którym znajduje się plik/katalog
C:\data

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.

2
Zadanie
C# SELF, poziom 39, lekcja 2
Niedostępne
Przeglądanie zawartości katalogu
Przeglądanie zawartości katalogu
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION