CodeGym /Courses /C# SELF /Getting information about files and directories

Getting information about files and directories

C# SELF
Level 39, Lesson 2
Available

1. Introduction

In Windows (and beyond) every file and directory has a set of properties (metadata): full path, name, extension, size, creation/modify/access dates, attributes, etc. Imagine: a file is not just bytes but a whole profile you can read using FileInfo and DirectoryInfo.

Property Description Example
Full path Full name of the file/folder
C:\data\myfile.txt
Name Name without the path
myfile.txt
Extension .txt, .csv, .jpg, etc.
.txt
Size (Bytes) File size in bytes
4096
Creation date When the file/folder was created
2024-04-16 19:30:10
Modification date When the content was last changed
2024-05-01 18:14:02
Attributes For example, ReadOnly, Hidden, etc.
ReadOnly, Hidden
Parent folder The folder that contains the file/directory
C:\data

2. Deep work with file properties

Detailed analysis of timestamps

The CreationTime, LastWriteTime, LastAccessTime properties return DateTime and their behavior depends on the file system and file operations.


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

if (fileInfo.Exists)
{
    Console.WriteLine($"Created: {fileInfo.CreationTime:yyyy-MM-dd HH:mm:ss}");
    Console.WriteLine($"Modified: {fileInfo.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
    Console.WriteLine($"Accessed: {fileInfo.LastAccessTime:yyyy-MM-dd HH:mm:ss}");
    
    // Difference between creation and last modification
    var age = fileInfo.LastWriteTime - fileInfo.CreationTime;
    Console.WriteLine($"File was modified during: {age.TotalDays:F1} days");
}

Interestingly, when copying a file the creation date is usually updated to the current time, but the modification date might be preserved from the original. This matters for backups and activity analysis.

Working with extensions and file names

The FullName, Name and Extension properties look simple but there are nuances: missing extension, compound extensions like .tar.gz and hidden files that start with a dot.


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($"Full name: {file.FullName}");
    Console.WriteLine($"Name: {file.Name}");
    Console.WriteLine($"Extension: '{file.Extension}'");
    
    // Name without extension
    string nameWithoutExtension = Path.GetFileNameWithoutExtension(file.Name);
    Console.WriteLine($"Name without extension: {nameWithoutExtension}");
    Console.WriteLine("---");
}

File sizes and formatting

The Length property returns size in bytes; it's more convenient for users to see KB/MB/GB. Helper function:


static string FormatFileSize(long bytes)
{
    string[] suffixes = { "B", "KB", "MB", "GB", "TB" };
    int counter = 0;
    decimal number = bytes;
    
    while (Math.Round(number / 1024) >= 1)
    {
        number /= 1024;
        counter++;
    }
    
    return $"{number:N1} {suffixes[counter]}";
}

// Usage
var file = new FileInfo("bigfile.zip");
if (file.Exists)
{
    Console.WriteLine($"File size: {FormatFileSize(file.Length)}");
}

3. Advanced work with file attributes

Attributes are represented by the FileAttributes enum (a set of bit flags), so a file can have multiple attributes at once. It's convenient to check them via HasFlag.


var fileInfo = new FileInfo("important.txt");

Console.WriteLine($"File attributes: {fileInfo.Attributes}");

// Check specific attributes
if (fileInfo.Attributes.HasFlag(FileAttributes.Hidden))
{
    Console.WriteLine("File is hidden!");
}

if (fileInfo.Attributes.HasFlag(FileAttributes.ReadOnly))
{
    Console.WriteLine("File is write-protected!");
}

if (fileInfo.Attributes.HasFlag(FileAttributes.System))
{
    Console.WriteLine("This is a system file!");
}

You can change attributes programmatically:


// Make file hidden
fileInfo.Attributes |= FileAttributes.Hidden;

// Remove the ReadOnly attribute
fileInfo.Attributes &= ~FileAttributes.ReadOnly;

// Set multiple attributes at once
fileInfo.Attributes = FileAttributes.ReadOnly | FileAttributes.Hidden;

4. Advanced work with directories

Searching files by patterns

GetFiles() and GetDirectories() accept patterns and help filter contents.


var dir = new DirectoryInfo(@"C:\Projects");

if (dir.Exists)
{
    // Find all text files
    var textFiles = dir.GetFiles("*.txt");
    Console.WriteLine($"Text files found: {textFiles.Length}");
    
    // Find all files starting with "temp"
    var tempFiles = dir.GetFiles("temp*");
    
    // Find all image files
    var imageExtensions = new[] { "*.jpg", "*.png", "*.gif", "*.bmp" };
    var allImages = imageExtensions.SelectMany(ext => dir.GetFiles(ext)).ToArray();
    
    Console.WriteLine($"Images found: {allImages.Length}");
}

Recursive search in subfolders

To traverse all subfolders use SearchOption.AllDirectories.


var dir = new DirectoryInfo(@"C:\Development");

// Find all C# files in all subfolders
var csharpFiles = dir.GetFiles("*.cs", SearchOption.AllDirectories);
Console.WriteLine($"Total .cs files found: {csharpFiles.Length}");

// Show first 10 files with their paths
foreach (var file in csharpFiles.Take(10))
{
    Console.WriteLine($"{file.FullName} ({FormatFileSize(file.Length)})");
}

Directory content analysis

An example summary analysis: number of files/folders, total size, distribution by extension and top-5 largest files.


static void AnalyzeDirectory(DirectoryInfo dir)
{
    if (!dir.Exists)
    {
        Console.WriteLine("Directory does not exist!");
        return;
    }
    
    var files = dir.GetFiles();
    var subdirs = dir.GetDirectories();
    
    Console.WriteLine($"Analyzing directory: {dir.FullName}");
    Console.WriteLine($"Files: {files.Length}, Subdirectories: {subdirs.Length}");
    
    if (files.Length == 0)
    {
        Console.WriteLine("No files found.");
        return;
    }
    
    long totalSize = files.Sum(f => f.Length);
    Console.WriteLine($"Total files size: {FormatFileSize(totalSize)}");
    
    // Group by extensions
    var byExtension = files.GroupBy(f => f.Extension.ToLower())
                           .OrderByDescending(g => g.Sum(f => f.Length));
    
    Console.WriteLine("\nDistribution by file types:");
    foreach (var group in byExtension)
    {
        string ext = string.IsNullOrEmpty(group.Key) ? "(no extension)" : group.Key;
        long groupSize = group.Sum(f => f.Length);
        Console.WriteLine($"  {ext}: {group.Count()} files, {FormatFileSize(groupSize)}");
    }
    
    // Top-5 largest files
    var largestFiles = files.OrderByDescending(f => f.Length).Take(5);
    Console.WriteLine("\nLargest files:");
    foreach (var file in largestFiles)
    {
        Console.WriteLine($"  {file.Name}: {FormatFileSize(file.Length)}");
    }
}

5. Optimized directory size calculation

For large folders prefer EnumerateFiles()/EnumerateDirectories() over GetFiles(), handle exceptions and optionally show progress.


static long GetDirectorySizeAdvanced(DirectoryInfo dir, bool showProgress = false)
{
    long totalSize = 0;
    int fileCount = 0;
    var inaccessibleDirs = new List<string>();
    
    try
    {
        // Use EnumerateFiles for big directories (lazy loading)
        foreach (var file in dir.EnumerateFiles())
        {
            try
            {
                totalSize += file.Length;
                fileCount++;
                
                if (showProgress && fileCount % 1000 == 0)
                {
                    Console.WriteLine($"Processed files: {fileCount}, size: {FormatFileSize(totalSize)}");
                }
            }
            catch (UnauthorizedAccessException)
            {
                // File is inaccessible, skip
            }
            catch (IOException)
            {
                // Problem reading file, skip
            }
        }
        
        // Recursively process subdirectories
        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($"Inaccessible directories: {inaccessibleDirs.Count}");
    }
    
    return totalSize;
}

// Usage
var targetDir = new DirectoryInfo(@"C:\Users");
Console.WriteLine("Starting directory size calculation...");
long size = GetDirectorySizeAdvanced(targetDir, showProgress: true);
Console.WriteLine($"Total size: {FormatFileSize(size)}");

6. Practical uses of metadata

Search files by date

Find files modified in a given period (useful for cleanup, analysis or audit).


static void FindFilesByDate(DirectoryInfo dir, DateTime fromDate, DateTime toDate)
{
    Console.WriteLine($"Searching files from {fromDate:yyyy-MM-dd} to {toDate:yyyy-MM-dd}");
    
    var matchingFiles = dir.GetFiles("*", SearchOption.AllDirectories)
                           .Where(f => f.LastWriteTime >= fromDate && f.LastWriteTime <= toDate)
                           .OrderByDescending(f => f.LastWriteTime);
    
    Console.WriteLine($"Files found: {matchingFiles.Count()}");
    
    foreach (var file in matchingFiles.Take(20))
    {
        Console.WriteLine($"{file.LastWriteTime:yyyy-MM-dd HH:mm} - {file.Name} ({FormatFileSize(file.Length)})");
    }
}

// Example: find all files modified in the last week
var dir = new DirectoryInfo(@"C:\Documents");
var weekAgo = DateTime.Now.AddDays(-7);
FindFilesByDate(dir, weekAgo, DateTime.Now);

Finding duplicate files

A quick method is to group by size. For accuracy you can add hash comparison of contents.


static void FindPotentialDuplicates(DirectoryInfo dir)
{
    Console.WriteLine($"Searching potential duplicates in {dir.FullName}");
    
    var files = dir.GetFiles("*", SearchOption.AllDirectories)
                   .Where(f => f.Length > 0) // Exclude empty files
                   .GroupBy(f => f.Length)
                   .Where(g => g.Count() > 1) // Only groups with multiple files
                   .OrderByDescending(g => g.Key); // Sort by size
    
    foreach (var sizeGroup in files.Take(10))
    {
        Console.WriteLine($"\nFiles sized {FormatFileSize(sizeGroup.Key)} ({sizeGroup.Count()} items):");
        foreach (var file in sizeGroup)
        {
            Console.WriteLine($"  {file.FullName}");
            Console.WriteLine($"    Modified: {file.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
        }
    }
}

Monitoring changes in a directory

Show files changed in the last N minutes.


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($"Files changed in the last {minutesBack} minutes:");
    
    if (!recentFiles.Any())
    {
        Console.WriteLine("No changes found.");
        return;
    }
    
    foreach (var file in recentFiles)
    {
        var minutesAgo = (DateTime.Now - file.LastWriteTime).TotalMinutes;
        Console.WriteLine($"{file.Name} - {minutesAgo:F0} min. ago ({FormatFileSize(file.Length)})");
    }
}

7. Working with parent directories

The Directory property on FileInfo and the Parent on DirectoryInfo let you move up the hierarchy.


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

Console.WriteLine($"File: {file.Name}");
Console.WriteLine($"Folder: {file.Directory.Name}");
Console.WriteLine($"Parent folder: {file.Directory.Parent.Name}");
Console.WriteLine($"Project root folder: {file.Directory.Parent.Parent.Name}");

// You can walk up the hierarchy to the root
var currentDir = file.Directory;
while (currentDir.Parent != null)
{
    Console.WriteLine($"Level: {currentDir.Name}");
    currentDir = currentDir.Parent;
}
Console.WriteLine($"Root: {currentDir.Name}");

8. Pitfalls and common mistakes

1. Caching. FileInfo and DirectoryInfo objects cache values. If the object changed after creation the data can be stale. Use Refresh() to update.


var file = new FileInfo("test.txt");
file.Refresh(); // Refresh metadata

2. Access exceptions. Some files and folders are inaccessible: handle UnauthorizedAccessException and other access errors.


try
{
    var files = new DirectoryInfo(path).GetFiles();
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("No access");
}

3. Timestamps. When copying CreationTime may change while LastWriteTime might be preserved. This affects reports and sync algorithms.


File.Copy("a.txt", "b.txt");

4. Performance. GetFiles() loads everything at once and can be slow on big folders. Prefer EnumerateFiles() for lazy enumeration.

2
Task
C# SELF, level 39, lesson 2
Locked
Directory Content Listing
Directory Content Listing
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION