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();
GO TO FULL VERSION