JavaRush /Курси /C# SELF /Порівняння підходів File

Порівняння підходів File vs FileInfo

C# SELF
Рівень 39 , Лекція 3
Відкрита

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();
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ