CodeGym /Courses /C# SELF /Comparing approaches File<...

Comparing approaches File vs FileInfo

C# SELF
Level 39, Lesson 3
Available

1. Introduction

In .NET there are two philosophically different groups of classes for working with files and folders:

Static classes: File, Directory — work like a set of utility functions: you call a method, pass a path, get a result.

Instance classes: FileInfo, DirectoryInfo — represent specific files and folders as objects with their own properties and methods.

Why both approaches? It's a compromise between ease of use (static methods) and object-oriented flexibility (instance classes). Each is convenient in its scenarios.

2. Philosophy of static classes

Classes File and Directory follow the "Just do it!" principle. They provide a straightforward interface to perform file system operations without the need to create intermediate objects.

// Check if a file exists
if (File.Exists("document.txt"))
{
    // Read all text
    string content = File.ReadAllText("document.txt");
    
    // Create a backup
    File.Copy("document.txt", "document_backup.txt");
    
    // Delete the original
    File.Delete("document.txt");
}

This is handy for simple, one-off operations: you don't need to think about objects, their lifecycle or state — just call the method with the file path.

Directory works similarly:

// Create a folder
Directory.CreateDirectory(@"C:\MyProject\Data");

// Get all text files
string[] textFiles = Directory.GetFiles(@"C:\MyProject", "*.txt");

// Check if a folder exists
if (Directory.Exists(@"C:\Temp"))
{
    Directory.Delete(@"C:\Temp", recursive: true);
}

3. Comparing approaches with practical examples

Example 1: Simple file copy

Static approach:

string sourcePath = "original.txt";
string destinationPath = "copy.txt";

if (File.Exists(sourcePath))
{
    File.Copy(sourcePath, destinationPath, overwrite: true);
    Console.WriteLine("File copied");
}
else
{
    Console.WriteLine("Source file not found");
}

Instance approach:

var sourceFile = new FileInfo("original.txt");
var destinationFile = new FileInfo("copy.txt");

if (sourceFile.Exists)
{
    sourceFile.CopyTo(destinationFile.FullName, overwrite: true);
    Console.WriteLine("File copied");
}
else
{
    Console.WriteLine("Source file not found");
}

Here the static approach is more concise. But what if you need additional info?

Example 2: Copy with size check

Static approach:

string sourcePath = "largefile.zip";
string destinationPath = "backup.zip";

if (File.Exists(sourcePath))
{
    var fileInfo = new FileInfo(sourcePath); // You still had to create an object!
    if (fileInfo.Length > 100 * 1024 * 1024) // More than 100 MB
    {
        Console.WriteLine($"Warning: copying a large file ({fileInfo.Length / 1024 / 1024} MB)");
    }
    
    File.Copy(sourcePath, destinationPath, overwrite: true);
}

Instance approach:

var sourceFile = new FileInfo("largefile.zip");

if (sourceFile.Exists)
{
    if (sourceFile.Length > 100 * 1024 * 1024) // More than 100 MB
    {
        Console.WriteLine($"Warning: copying a large file ({sourceFile.Length / 1024 / 1024} MB)");
    }
    
    sourceFile.CopyTo("backup.zip", overwrite: true);
}

Here the object approach feels more natural: you work with the file as an object and use its properties.

Example 3: Analyzing folder contents

Static approach:

string folderPath = @"C:\Documents";

if (Directory.Exists(folderPath))
{
    string[] files = Directory.GetFiles(folderPath);
    string[] subdirs = Directory.GetDirectories(folderPath);
    
    Console.WriteLine($"Files: {files.Length}, Folders: {subdirs.Length}");
    
    // To get sizes you still need FileInfo objects
    long totalSize = 0;
    foreach (string filePath in files)
    {
        var fileInfo = new FileInfo(filePath);
        totalSize += fileInfo.Length;
    }
    
    Console.WriteLine($"Total size: {totalSize / 1024} KB");
}

Instance approach:

var folder = new DirectoryInfo(@"C:\Documents");

if (folder.Exists)
{
    var files = folder.GetFiles();
    var subdirs = folder.GetDirectories();
    
    Console.WriteLine($"Files: {files.Length}, Folders: {subdirs.Length}");
    
    long totalSize = files.Sum(f => f.Length); // Info is already available!
    Console.WriteLine($"Total size: {totalSize / 1024} KB");
}

Here DirectoryInfo wins: the collections of FileInfo[] already contain metadata.

4. Criteria for choosing an approach

Use static classes (File/Directory) when:

  • The operation is simple and one-off. Need to quickly check a file, delete it or read its content — static methods are ideal.
// Simple configuration read
if (File.Exists("config.json"))
{
    string config = File.ReadAllText("config.json");
    // process config...
}
  • You don't need file property info. If size, dates or attributes don't matter — static calls are shorter and fit better.
  • You're working with paths, not files as objects. When logic operates on string paths, static methods feel more natural.

Use instance classes (FileInfo/DirectoryInfo) when:

  • You need a lot of info about a single file/folder. Properties like Length, CreationTime, Attributes are available directly.
var logFile = new FileInfo("application.log");
Console.WriteLine($"Log size: {logFile.Length / 1024} KB");
Console.WriteLine($"Last modified: {logFile.LastWriteTime}");
Console.WriteLine($"Location: {logFile.Directory.FullName}");
  • You're doing multiple operations on the same file. Create the object once — reuse it for different actions.
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($"Document archived, copy created {backup.Name}");
}
  • You're working with collections of files. GetFiles() and GetDirectories() return objects with full info.
  • You need an object-oriented architecture. Files/folders as objects are easier to pass around, store and use in LINQ.

5. Performance and caching specifics

Caching in instance classes

One key feature of FileInfo/DirectoryInfo is metadata caching. On first access to a property (for example, Length or CreationTime) .NET makes a system call, loads all info and caches it inside the object.

var file = new FileInfo("document.txt");

// First access - system call to load all metadata
long size = file.Length;

// Subsequent accesses use cache - very fast
DateTime created = file.CreationTime;
DateTime modified = file.LastWriteTime;
bool readOnly = file.IsReadOnly;

If you need multiple properties of the same file, the object approach is more efficient: one system call instead of many.

Stale cache problem

The downside — information can become stale if the file is changed outside your program. Use Refresh():

var file = new FileInfo("data.txt");
Console.WriteLine($"Size: {file.Length}"); // e.g., 1000 bytes

// Meanwhile another program modifies the file...

Console.WriteLine($"Size: {file.Length}"); // Still 1000 bytes from cache!

// Force update
file.Refresh();
Console.WriteLine($"Size: {file.Length}"); // Now the current size

Static calls always go directly to the file system, so they don't have this problem.

Bulk operations

On large sets of files the API choice affects performance:

// Static approach — many system calls
string[] files = Directory.GetFiles(@"C:\Photos");
foreach (string filePath in files)
{
    var info = new FileInfo(filePath); // System call per file
    if (info.Length > 10 * 1024 * 1024) // More than 10 MB
    {
        Console.WriteLine($"Large photo: {info.Name}");
    }
}

// Instance approach — one system call per folder
var photosDir = new DirectoryInfo(@"C:\Photos");
foreach (var file in photosDir.GetFiles()) // Info is loaded right away
{
    if (file.Length > 10 * 1024 * 1024)
    {
        Console.WriteLine($"Large photo: {file.Name}");
    }
}

6. Differences in API and capabilities

Unique capabilities of instance classes

var file = new FileInfo(@"C:\Projects\MyApp\source\Program.cs");

// Navigate the folder hierarchy
DirectoryInfo projectDir = file.Directory.Parent; // MyApp
DirectoryInfo sourceDir = file.Directory; // source

// Detailed file info
Console.WriteLine($"Extension: {file.Extension}");
Console.WriteLine($"Read only: {file.IsReadOnly}");
Console.WriteLine($"Attributes: {file.Attributes}");

// Working with directory as an object
var dir = new DirectoryInfo(@"C:\Projects");
DirectoryInfo parent = dir.Parent; // C:\
DirectoryInfo root = dir.Root;     // C:\

Unique capabilities of static classes

// Read and write text in one line
string content = File.ReadAllText("config.txt");
File.WriteAllText("output.txt", "Hello World");

// Work with lines
string[] lines = File.ReadAllLines("data.txt");
File.WriteAllLines("output.txt", new[] { "Line 1", "Line 2" });

// Append text to a file
File.AppendAllText("log.txt", $"{DateTime.Now}: Application started\n");

// Work with bytes
byte[] data = File.ReadAllBytes("image.jpg");
File.WriteAllBytes("copy.jpg", data);

7. Practical recommendations

Scenario 1: Backup utility

For copying with checks on sizes and dates instance classes are more convenient:

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));
        
        // Copy only if the file is newer or doesn't exist
        if (!backupFile.Exists || file.LastWriteTime > backupFile.LastWriteTime)
        {
            file.CopyTo(backupFile.FullName, overwrite: true);
            Console.WriteLine($"Copied: {file.Name} ({file.Length / 1024} KB)");
        }
    }
}

Scenario 2: Simple work with text files

For loading/saving simple data static methods are enough:

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))
    {
        // Create default settings
        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;
}

Scenario 3: File system analysis

For disk usage reports instance classes are ideal:

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 = "(no 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} MB");
    }
}

8. Common mistakes and pitfalls

Mistake: Mixing approaches unnecessarily

Sometimes people start with static methods and then create objects for metadata — resulting in redundant calls:

// Inefficient - two file system calls
if (File.Exists("document.txt"))
{
    var fileInfo = new FileInfo("document.txt"); // Duplicate existence check
    Console.WriteLine($"Size: {fileInfo.Length}");
}

// Better to use the object approach right away
var fileInfo = new FileInfo("document.txt");
if (fileInfo.Exists)
{
    Console.WriteLine($"Size: {fileInfo.Length}");
}

Mistake: Ignoring stale cache

When working for a long time with files that can be changed externally, refresh the cache with Refresh():

var logFile = new FileInfo("application.log");

while (true)
{
    logFile.Refresh(); // Update file info
    
    if (logFile.Length > 100 * 1024 * 1024) // 100 MB
    {
        // Archive the log
        logFile.MoveTo($"logs/archived_{DateTime.Now:yyyyMMdd_HHmmss}.log");
        break;
    }
    
    Thread.Sleep(60000); // Check every minute
}

Mistake: Wrong choice for bulk operations

When processing thousands of files the API choice is critical for performance:

// Slow - many small file system calls
string[] allFiles = Directory.GetFiles(@"C:\BigFolder", "*", SearchOption.AllDirectories);
var largeFiles = allFiles.Where(path => new FileInfo(path).Length > 1024 * 1024).ToList();

// Faster - use already available info
var folder = new DirectoryInfo(@"C:\BigFolder");
var largeFiles = folder.GetFiles("*", SearchOption.AllDirectories)
                      .Where(file => file.Length > 1024 * 1024)
                      .ToList();
2
Task
C# SELF, level 39, lesson 3
Locked
Folder Content Analysis
Folder Content Analysis
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION