1. Вступ
У .NET для роботи з файлами й каталогами існують дві концептуально різні групи класів:
Статичні класи: File, Directory — працюють як набір утилітних функцій: викликаєте метод, передаєте шлях, отримуєте результат.
Екземплярні класи: FileInfo, DirectoryInfo — представляють конкретні файли й каталоги як об’єкти з їхніми властивостями та методами.
Чому обидва підходи? Це компроміс між простотою використання (статичні методи) та об’єктноорієнтованою гнучкістю (екземплярні класи). Кожен зручний у своїх сценаріях.
2. Філософія статичних класів
Класи File і Directory дотримуються принципу «Просто роби!». Вони надають прямолінійний інтерфейс для виконання операцій із файловою системою без потреби створювати проміжні об’єкти.
// Перевірити існування файлу
if (File.Exists("document.txt"))
{
// Прочитати весь текст
string content = File.ReadAllText("document.txt");
// Створити резервну копію
File.Copy("document.txt", "document_backup.txt");
// Видалити оригінал
File.Delete("document.txt");
}
Це зручно для простих одноразових операцій: не потрібно думати про об’єкти, їхній життєвий цикл чи стан — просто викликаєте потрібний метод і передаєте шлях до файлу.
Аналогічно працює Directory:
// Створити каталог
Directory.CreateDirectory(@"C:\MyProject\Data");
// Отримати всі текстові файли
string[] textFiles = Directory.GetFiles(@"C:\MyProject", "*.txt");
// Перевірити існування каталогу
if (Directory.Exists(@"C:\Temp"))
{
Directory.Delete(@"C:\Temp", recursive: true);
}
3. Порівняння підходів на практичних прикладах
Приклад 1: Просте копіювання файлу
Статичний підхід:
string sourcePath = "original.txt";
string destinationPath = "copy.txt";
if (File.Exists(sourcePath))
{
File.Copy(sourcePath, destinationPath, overwrite: true);
Console.WriteLine("Файл скопійовано");
}
else
{
Console.WriteLine("Вихідний файл не знайдено");
}
Екземплярний підхід:
var sourceFile = new FileInfo("original.txt");
var destinationFile = new FileInfo("copy.txt");
if (sourceFile.Exists)
{
sourceFile.CopyTo(destinationFile.FullName, overwrite: true);
Console.WriteLine("Файл скопійовано");
}
else
{
Console.WriteLine("Вихідний файл не знайдено");
}
Тут статичний підхід лаконічніший. Але що, якщо потрібна додаткова інформація?
Приклад 2: Копіювання з перевіркою розміру
Статичний підхід:
string sourcePath = "largefile.zip";
string destinationPath = "backup.zip";
if (File.Exists(sourcePath))
{
var fileInfo = new FileInfo(sourcePath); // Усе одно довелося створити об’єкт!
if (fileInfo.Length > 100 * 1024 * 1024) // Більше 100 МБ
{
Console.WriteLine($"Увага: копіюється великий файл ({fileInfo.Length / 1024 / 1024} МБ)");
}
File.Copy(sourcePath, destinationPath, overwrite: true);
}
Екземплярний підхід:
var sourceFile = new FileInfo("largefile.zip");
if (sourceFile.Exists)
{
if (sourceFile.Length > 100 * 1024 * 1024) // Більше 100 МБ
{
Console.WriteLine($"Увага: копіюється великий файл ({sourceFile.Length / 1024 / 1024} МБ)");
}
sourceFile.CopyTo("backup.zip", overwrite: true);
}
Тут об’єктний підхід природніший: працюєте з файлом як із об’єктом і використовуєте його властивості.
Приклад 3: Аналіз вмісту каталогу
Статичний підхід:
string folderPath = @"C:\Documents";
if (Directory.Exists(folderPath))
{
string[] files = Directory.GetFiles(folderPath);
string[] subdirs = Directory.GetDirectories(folderPath);
Console.WriteLine($"Файлів: {files.Length}, Каталогів: {subdirs.Length}");
// Щоб отримати розміри, усе одно потрібні об’єкти FileInfo
long totalSize = 0;
foreach (string filePath in files)
{
var fileInfo = new FileInfo(filePath);
totalSize += fileInfo.Length;
}
Console.WriteLine($"Загальний розмір: {totalSize / 1024} КБ");
}
Екземплярний підхід:
var folder = new DirectoryInfo(@"C:\Documents");
if (folder.Exists)
{
var files = folder.GetFiles();
var subdirs = folder.GetDirectories();
Console.WriteLine($"Файлів: {files.Length}, Каталогів: {subdirs.Length}");
long totalSize = files.Sum(f => f.Length); // Інформація вже доступна!
Console.WriteLine($"Загальний розмір: {totalSize / 1024} КБ");
}
Тут DirectoryInfo виграє: колекції FileInfo[] вже містять метадані.
4. Критерії вибору підходу
Використовуйте статичні класи (File/Directory), коли:
- Операція проста й одноразова. Потрібно швидко перевірити існування файлу, видалити його або прочитати вміст — статичні методи ідеальні.
// Просте читання конфігурації
if (File.Exists("config.json"))
{
string config = File.ReadAllText("config.json");
// оброблення конфігурації...
}
- Не потрібна інформація про властивості файлу. Якщо не важливі розмір, дати чи атрибути — статичні виклики коротші й краще підходять.
- Працюєте зі шляхами, а не з файлами як об’єктами. Коли логіка оперує рядковими шляхами, статичні методи доречніші.
Використовуйте екземплярні класи (FileInfo/DirectoryInfo), коли:
- Потрібно багато інформації про один файл/каталог. Властивості на кшталт Length, CreationTime, Attributes доступні безпосередньо.
var logFile = new FileInfo("application.log");
Console.WriteLine($"Розмір журналу: {logFile.Length / 1024} КБ");
Console.WriteLine($"Остання зміна: {logFile.LastWriteTime}");
Console.WriteLine($"Розташування: {logFile.Directory.FullName}");
- Виконуєте багато операцій з одним файлом. Створили об’єкт один раз — використовуєте для різних дій.
var document = new FileInfo("report.docx");
if (document.Exists)
{
var backup = document.CopyTo($"report_backup_{DateTime.Now:yyyyMMdd}.docx");
document.MoveTo("archive/report.docx");
Console.WriteLine($"Документ заархівовано, створено копію {backup.Name}");
}
- Працюєте з колекціями файлів. GetFiles() і GetDirectories() повертають об’єкти з повною інформацією.
- Потрібна об’єктноорієнтована архітектура. Файли/каталоги як об’єкти легше передавати, зберігати й використовувати в LINQ.
5. Особливості продуктивності та кешування
Кешування в екземплярних класах
Одна з ключових переваг FileInfo/DirectoryInfo — кешування метаданих. При першому зверненні до властивості (наприклад, Length або CreationTime) .NET виконує системний виклик, підтягує потрібну інформацію й кешує її всередині об’єкта.
var file = new FileInfo("document.txt");
// Перше звернення — системний виклик для завантаження всіх метаданих
long size = file.Length;
// Наступні звернення використовують кеш — дуже швидко
DateTime created = file.CreationTime;
DateTime modified = file.LastWriteTime;
bool readOnly = file.IsReadOnly;
Якщо потрібно кілька властивостей одного файлу, об’єктний підхід ефективніший: один системний виклик замість кількох.
Проблема застарілого кешу
Зворотний бік — інформація може застаріти, якщо файл змінився поза межами вашої програми. Користуйтеся Refresh():
var file = new FileInfo("data.txt");
Console.WriteLine($"Розмір: {file.Length}"); // Наприклад, 1000 байт
// У цей час інша програма змінює файл...
Console.WriteLine($"Розмір: {file.Length}"); // Усе ще 1000 байт із кешу!
// Примусове оновлення
file.Refresh();
Console.WriteLine($"Розмір: {file.Length}"); // Тепер актуальний розмір
Статичні виклики завжди звертаються безпосередньо до файлової системи, тому такої проблеми вони не мають.
Масові операції
На великих наборах файлів вибір API впливає на продуктивність:
// Статичний підхід — багато системних викликів
string[] files = Directory.GetFiles(@"C:\Photos");
foreach (string filePath in files)
{
var info = new FileInfo(filePath); // Системний виклик для кожного файлу
if (info.Length > 10 * 1024 * 1024) // Більше 10 МБ
{
Console.WriteLine($"Велике фото: {info.Name}");
}
}
// Екземплярний підхід — один системний виклик на каталог
var photosDir = new DirectoryInfo(@"C:\Photos");
foreach (var file in photosDir.GetFiles()) // Інформація завантажується відразу
{
if (file.Length > 10 * 1024 * 1024)
{
Console.WriteLine($"Велике фото: {file.Name}");
}
}
6. Відмінності в API та можливостях
Унікальні можливості екземплярних класів
var file = new FileInfo(@"C:\Projects\MyApp\source\Program.cs");
// Навігація ієрархією каталогів
DirectoryInfo projectDir = file.Directory.Parent; // MyApp
DirectoryInfo sourceDir = file.Directory; // source
// Детальна інформація про файл
Console.WriteLine($"Розширення: {file.Extension}");
Console.WriteLine($"Лише для читання: {file.IsReadOnly}");
Console.WriteLine($"Атрибути: {file.Attributes}");
// Робота з каталогом як об’єктом
var dir = new DirectoryInfo(@"C:\Projects");
DirectoryInfo parent = dir.Parent; // C:\
DirectoryInfo root = dir.Root; // C:\
Унікальні можливості статичних класів
// Читання та запис тексту в один рядок
string content = File.ReadAllText("config.txt");
File.WriteAllText("output.txt", "Hello World");
// Робота з рядками
string[] lines = File.ReadAllLines("data.txt");
File.WriteAllLines("output.txt", new[] { "Line 1", "Line 2" });
// Додавання тексту до файлу
File.AppendAllText("log.txt", $"{DateTime.Now}: Application started\n");
// Робота з байтами
byte[] data = File.ReadAllBytes("image.jpg");
File.WriteAllBytes("copy.jpg", data);
7. Практичні рекомендації
Сценарій 1: Утиліта резервного копіювання
Для копіювання з перевіркою розмірів і дат зручніші екземплярні класи:
public void BackupDirectory(string sourcePath, string backupPath)
{
var sourceDir = new DirectoryInfo(sourcePath);
var backupDir = new DirectoryInfo(backupPath);
if (!backupDir.Exists)
backupDir.Create();
foreach (var file in sourceDir.GetFiles())
{
var backupFile = new FileInfo(Path.Combine(backupPath, file.Name));
// Копіюємо лише якщо файл новіший або не існує
if (!backupFile.Exists || file.LastWriteTime > backupFile.LastWriteTime)
{
file.CopyTo(backupFile.FullName, overwrite: true);
Console.WriteLine($"Скопійовано: {file.Name} ({file.Length / 1024} КБ)");
}
}
}
Сценарій 2: Проста робота з текстовими файлами
Для завантаження/збереження простих даних достатньо статичних методів:
public void SaveUserPreferences(string username, string theme, bool notifications)
{
string configPath = "user.config";
string[] settings = {
$"Username={username}",
$"Theme={theme}",
$"Notifications={notifications}"
};
File.WriteAllLines(configPath, settings);
}
public Dictionary<string, string> LoadUserPreferences()
{
string configPath = "user.config";
var preferences = new Dictionary<string, string>();
if (!File.Exists(configPath))
{
// Створюємо налаштування за замовчуванням
SaveUserPreferences("User", "Light", true);
return LoadUserPreferences();
}
string[] lines = File.ReadAllLines(configPath);
foreach (string line in lines)
{
if (line.Contains('='))
{
string[] parts = line.Split('=', 2);
preferences[parts[0]] = parts[1];
}
}
return preferences;
}
Сценарій 3: Аналіз файлової системи
Для звітів про дисковий простір ідеально підходять екземплярні класи:
public void AnalyzeDiskUsage(string path)
{
var directory = new DirectoryInfo(path);
var report = new Dictionary<string, long>();
foreach (var file in directory.GetFiles("*", SearchOption.AllDirectories))
{
string extension = file.Extension.ToLower();
if (string.IsNullOrEmpty(extension))
extension = "(без розширення)";
if (!report.ContainsKey(extension))
report[extension] = 0;
report[extension] += file.Length;
}
var sortedReport = report.OrderByDescending(kvp => kvp.Value);
foreach (var item in sortedReport.Take(10))
{
Console.WriteLine($"{item.Key}: {item.Value / 1024 / 1024} МБ");
}
}
8. Типові помилки та підводні камені
Помилка: Змішування підходів без потреби
Інколи починають зі статичних методів, а потім створюють об’єкти для метаданих — виходить зайве дублювання звернень:
// Неефективно — два звернення до файлової системи
if (File.Exists("document.txt"))
{
var fileInfo = new FileInfo("document.txt"); // Дублювання перевірки існування
Console.WriteLine($"Розмір: {fileInfo.Length}");
}
// Краще одразу використовувати об’єктний підхід
var fileInfo = new FileInfo("document.txt");
if (fileInfo.Exists)
{
Console.WriteLine($"Розмір: {fileInfo.Length}");
}
Помилка: Ігнорування застарілого кешу
Під час тривалої роботи з файлами, які можуть змінюватися ззовні, оновлюйте кеш за допомогою Refresh():
var logFile = new FileInfo("application.log");
while (true)
{
logFile.Refresh(); // Оновлюємо інформацію про файл
if (logFile.Length > 100 * 1024 * 1024) // 100 МБ
{
// Архівуємо журнал
logFile.MoveTo($"logs/archived_{DateTime.Now:yyyyMMdd_HHmmss}.log");
break;
}
Thread.Sleep(60000); // Перевіряємо щохвилини
}
Помилка: Неправильний вибір для масових операцій
Під час оброблення тисяч файлів вибір API критичний для продуктивності:
// Повільно — багато дрібних звернень до файлової системи
string[] allFiles = Directory.GetFiles(@"C:\BigFolder", "*", SearchOption.AllDirectories);
var largeFiles = allFiles.Where(path => new FileInfo(path).Length > 1024 * 1024).ToList();
// Швидше — використовуємо готову інформацію
var folder = new DirectoryInfo(@"C:\BigFolder");
var largeFiles = folder.GetFiles("*", SearchOption.AllDirectories)
.Where(file => file.Length > 1024 * 1024)
.ToList();
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ