1. Введение
В Windows (и не только) у каждого файла и директории есть набор свойств (метаданных): полный путь, имя, расширение, размер, даты создания/изменения/доступа, атрибуты и т.д. Представьте: файл — это не только байты, но и целая анкета, которую можно прочитать с помощью FileInfo и DirectoryInfo.
| Свойство | Описание | Пример |
|---|---|---|
| Полный путь | Полное имя файла/папки | |
| Имя | Имя без пути | |
| Расширение | .txt, .csv, .jpg и т.д. | |
| Размер (Bytes) | Размер файла в байтах | |
| Дата создания | Когда был создан файл/папка | |
| Дата изменения | Когда в последний раз менялось содержимое | |
| Атрибуты | Например, только для чтения, скрытый и т.д. | |
| Родительская папка | Папка, в которой находится файл/директория | |
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() для ленивого перечисления.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ