1. Вступ
У Windows (і не тільки) у кожного файлу й каталогу є набір властивостей (метаданих): повний шлях, імʼя, розширення, розмір, дати створення/зміни/доступу, атрибути тощо. Уявіть: файл — це не лише байти, а й ціла анкета, яку можна прочитати за допомогою FileInfo і DirectoryInfo.
| Властивість | Опис | Приклад |
|---|---|---|
| Повний шлях | Повна назва файлу/каталогу | |
| Імʼя | Імʼя без шляху | |
| Розширення | .txt, .csv, .jpg тощо. | |
| Розмір (байти) | Розмір файлу в байтах | |
| Дата створення | Коли було створено файл/каталог | |
| Дата зміни | Коли востаннє змінювався вміст | |
| Атрибути | Наприклад, тільки для читання, прихований тощо. | |
| Батьківський каталог | Каталог, у якому міститься файл або підкаталог | |
2. Поглиблена робота з властивостями файлів
Детальний аналіз часових міток
Властивості CreationTime, LastWriteTime і LastAccessTime повертають DateTime, а їхня поведінка залежить від файлової системи та операцій із файлом.
var fileInfo = new FileInfo("document.txt");
if (fileInfo.Exists)
{
Console.WriteLine($"Створено: {fileInfo.CreationTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"Змінено: {fileInfo.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"Відкривався: {fileInfo.LastAccessTime:yyyy-MM-dd HH:mm:ss}");
// Різниця між створенням і останньою зміною
var age = fileInfo.LastWriteTime - fileInfo.CreationTime;
Console.WriteLine($"Файл змінювався протягом: {age.TotalDays:F1} днів");
}
Цікаво, що під час копіювання файлу дата створення зазвичай оновлюється на поточну, але дата зміни може зберегтися від оригіналу. Це важливо для резервного копіювання та аналізу активності.
Робота з розширеннями та іменами файлів
Властивості FullName, Name і Extension здаються простими, але є нюанси: відсутність розширення, складені розширення на кшталт .tar.gz, а також приховані файли, що починаються з крапки.
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($"Повне імʼя: {file.FullName}");
Console.WriteLine($"Імʼя: {file.Name}");
Console.WriteLine($"Розширення: '{file.Extension}'");
// Імʼя без розширення
string nameWithoutExtension = Path.GetFileNameWithoutExtension(file.Name);
Console.WriteLine($"Імʼя без розширення: {nameWithoutExtension}");
Console.WriteLine("---");
}
Розмір файлів і форматування
Властивість Length повертає розмір у байтах; користувачеві зручніше бачити КБ/МБ/ГБ. Допоміжна функція:
static string FormatFileSize(long bytes)
{
string[] suffixes = { "Б", "КБ", "МБ", "ГБ", "ТБ" };
int counter = 0;
decimal number = bytes;
while (Math.Round(number / 1024) >= 1)
{
number /= 1024;
counter++;
}
return $"{number:N1} {suffixes[counter]}";
}
// Використання
var file = new FileInfo("bigfile.zip");
if (file.Exists)
{
Console.WriteLine($"Розмір файлу: {FormatFileSize(file.Length)}");
}
3. Розширена робота з атрибутами файлів
Атрибути представлені переліченням FileAttributes (набір бітових прапорців), тому у файлу може бути кілька атрибутів одночасно. Перевіряти зручно через HasFlag.
var fileInfo = new FileInfo("important.txt");
Console.WriteLine($"Атрибути файлу: {fileInfo.Attributes}");
// Перевіряємо конкретні атрибути
if (fileInfo.Attributes.HasFlag(FileAttributes.Hidden))
{
Console.WriteLine("Файл прихований!");
}
if (fileInfo.Attributes.HasFlag(FileAttributes.ReadOnly))
{
Console.WriteLine("Файл захищений від запису!");
}
if (fileInfo.Attributes.HasFlag(FileAttributes.System))
{
Console.WriteLine("Це системний файл!");
}
Атрибути можна змінювати програмно:
// Зробити файл прихованим
fileInfo.Attributes |= FileAttributes.Hidden;
// Прибрати атрибут «тільки для читання»
fileInfo.Attributes &= ~FileAttributes.ReadOnly;
// Встановити кілька атрибутів одразу
fileInfo.Attributes = FileAttributes.ReadOnly | FileAttributes.Hidden;
4. Розширена робота з каталогами
Пошук файлів за шаблонами
Методи GetFiles() та GetDirectories() приймають шаблони й допомагають фільтрувати вміст.
var dir = new DirectoryInfo(@"C:\Projects");
if (dir.Exists)
{
// Знайти всі текстові файли
var textFiles = dir.GetFiles("*.txt");
Console.WriteLine($"Знайдено текстових файлів: {textFiles.Length}");
// Знайти всі файли, що починаються з "temp"
var tempFiles = dir.GetFiles("temp*");
// Знайти всі файли зображень
var imageExtensions = new[] { "*.jpg", "*.png", "*.gif", "*.bmp" };
var allImages = imageExtensions.SelectMany(ext => dir.GetFiles(ext)).ToArray();
Console.WriteLine($"Знайдено зображень: {allImages.Length}");
}
Рекурсивний пошук у підкаталогах
Для обходу всіх підкаталогів використовуйте SearchOption.AllDirectories.
var dir = new DirectoryInfo(@"C:\Development");
// Знайти всі файли C# у всіх підкаталогах
var csharpFiles = dir.GetFiles("*.cs", SearchOption.AllDirectories);
Console.WriteLine($"Усього знайдено файлів .cs: {csharpFiles.Length}");
// Показати перші 10 файлів із їхніми шляхами
foreach (var file in csharpFiles.Take(10))
{
Console.WriteLine($"{file.FullName} ({FormatFileSize(file.Length)})");
}
Аналіз вмісту каталогу
Приклад зведеного аналізу: кількість файлів/каталогів, загальний розмір, розподіл за розширеннями та топ-5 найбільших файлів.
static void AnalyzeDirectory(DirectoryInfo dir)
{
if (!dir.Exists)
{
Console.WriteLine("Каталогу не існує!");
return;
}
var files = dir.GetFiles();
var subdirs = dir.GetDirectories();
Console.WriteLine($"Аналіз каталогу: {dir.FullName}");
Console.WriteLine($"Файлів: {files.Length}, Підкаталогів: {subdirs.Length}");
if (files.Length == 0)
{
Console.WriteLine("Файлів не знайдено.");
return;
}
long totalSize = files.Sum(f => f.Length);
Console.WriteLine($"Загальний розмір файлів: {FormatFileSize(totalSize)}");
// Групування за розширеннями
var byExtension = files.GroupBy(f => f.Extension.ToLower())
.OrderByDescending(g => g.Sum(f => f.Length));
Console.WriteLine("\nРозподіл за типами файлів:");
foreach (var group in byExtension)
{
string ext = string.IsNullOrEmpty(group.Key) ? "(без розширення)" : group.Key;
long groupSize = group.Sum(f => f.Length);
Console.WriteLine($" {ext}: {group.Count()} файлів, {FormatFileSize(groupSize)}");
}
// Топ-5 найбільших файлів
var largestFiles = files.OrderByDescending(f => f.Length).Take(5);
Console.WriteLine("\nНайбільші файли:");
foreach (var file in largestFiles)
{
Console.WriteLine($" {file.Name}: {FormatFileSize(file.Length)}");
}
}
5. Оптимізований підрахунок розміру каталогу
Для великих каталогів замість GetFiles() використовуйте ліниві перелічувачі EnumerateFiles()/EnumerateDirectories(), обробляйте винятки й за бажанням показуйте прогрес.
static long GetDirectorySizeAdvanced(DirectoryInfo dir, bool showProgress = false)
{
long totalSize = 0;
int fileCount = 0;
var inaccessibleDirs = new List<string>();
try
{
// Використовуємо EnumerateFiles для великих каталогів (ліниве перелічування)
foreach (var file in dir.EnumerateFiles())
{
try
{
totalSize += file.Length;
fileCount++;
if (showProgress && fileCount % 1000 == 0)
{
Console.WriteLine($"Оброблено файлів: {fileCount}, розмір: {FormatFileSize(totalSize)}");
}
}
catch (UnauthorizedAccessException)
{
// Файл недоступний, пропускаємо
}
catch (IOException)
{
// Проблеми з читанням файлу, пропускаємо
}
}
// Рекурсивно обробляємо підкаталоги
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($"Недоступних каталогів: {inaccessibleDirs.Count}");
}
return totalSize;
}
// Використання
var targetDir = new DirectoryInfo(@"C:\Users");
Console.WriteLine("Починаємо підрахунок розміру каталогу...");
long size = GetDirectorySizeAdvanced(targetDir, showProgress: true);
Console.WriteLine($"Загальний розмір: {FormatFileSize(size)}");
6. Практичні застосування метаданих
Пошук файлів за датою
Шукаємо файли, змінені у заданий період (зручно для прибирання, аналізу або аудиту).
static void FindFilesByDate(DirectoryInfo dir, DateTime fromDate, DateTime toDate)
{
Console.WriteLine($"Пошук файлів від {fromDate:yyyy-MM-dd} до {toDate:yyyy-MM-dd}");
var matchingFiles = dir.GetFiles("*", SearchOption.AllDirectories)
.Where(f => f.LastWriteTime >= fromDate && f.LastWriteTime <= toDate)
.OrderByDescending(f => f.LastWriteTime);
Console.WriteLine($"Знайдено файлів: {matchingFiles.Count()}");
foreach (var file in matchingFiles.Take(20))
{
Console.WriteLine($"{file.LastWriteTime:yyyy-MM-dd HH:mm} - {file.Name} ({FormatFileSize(file.Length)})");
}
}
// Приклад: знайти всі файли, змінені за останній тиждень
var dir = new DirectoryInfo(@"C:\Documents");
var weekAgo = DateTime.Now.AddDays(-7);
FindFilesByDate(dir, weekAgo, DateTime.Now);
Пошук дублікатів файлів
Швидкий метод — згрупувати за розміром. Для точності можна додати порівняння хешів вмісту.
static void FindPotentialDuplicates(DirectoryInfo dir)
{
Console.WriteLine($"Пошук потенційних дублікатів у {dir.FullName}");
var files = dir.GetFiles("*", SearchOption.AllDirectories)
.Where(f => f.Length > 0) // Вилучаємо порожні файли
.GroupBy(f => f.Length)
.Where(g => g.Count() > 1) // Лише групи з кількома файлами
.OrderByDescending(g => g.Key); // Сортуємо за розміром
foreach (var sizeGroup in files.Take(10))
{
Console.WriteLine($"\nФайли розміром {FormatFileSize(sizeGroup.Key)} ({sizeGroup.Count()} файлів):");
foreach (var file in sizeGroup)
{
Console.WriteLine($" {file.FullName}");
Console.WriteLine($" Змінено: {file.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
}
}
}
Моніторинг змін у каталозі
Показуємо файли, змінені за останні N хвилин.
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($"Файли, змінені за останні {minutesBack} хвилин:");
if (!recentFiles.Any())
{
Console.WriteLine("Змін не знайдено.");
return;
}
foreach (var file in recentFiles)
{
var minutesAgo = (DateTime.Now - file.LastWriteTime).TotalMinutes;
Console.WriteLine($"{file.Name} - {minutesAgo:F0} хв тому ({FormatFileSize(file.Length)})");
}
}
7. Робота з батьківськими каталогами
Властивість Directory у FileInfo і Parent у DirectoryInfo дозволяють підніматися ієрархією.
var file = new FileInfo(@"C:\Projects\MyApp\src\Program.cs");
Console.WriteLine($"Файл: {file.Name}");
Console.WriteLine($"Каталог: {file.Directory.Name}");
Console.WriteLine($"Батьківський каталог: {file.Directory.Parent.Name}");
Console.WriteLine($"Кореневий каталог проєкту: {file.Directory.Parent.Parent.Name}");
// Можна підніматися ієрархією до кореня
var currentDir = file.Directory;
while (currentDir.Parent != null)
{
Console.WriteLine($"Рівень: {currentDir.Name}");
currentDir = currentDir.Parent;
}
Console.WriteLine($"Корінь: {currentDir.Name}");
8. Пастки й типові помилки
1. Кешування. Обʼєкти FileInfo і DirectoryInfo кешують значення. Якщо обʼєкт змінювався після створення, дані можуть застаріти. Використовуйте Refresh() для актуалізації.
var file = new FileInfo("test.txt");
file.Refresh(); // Оновлення метаданих
2. Винятки доступу. Деякі файли й каталоги недоступні: обробляйте UnauthorizedAccessException та інші помилки доступу.
try
{
var files = new DirectoryInfo(path).GetFiles();
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Немає доступу.");
}
3. Часові мітки. Під час копіювання CreationTime може змінитися, а LastWriteTime — зберегтися. Це впливає на звіти та алгоритми синхронізації.
File.Copy("a.txt", "b.txt");
4. Продуктивність. GetFiles() завантажує все одразу й може гальмувати на великих каталогах. Краще використовуйте EnumerateFiles() для лінивого перелічування.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ