C#
.NET
file handling
directory traversal
programming tutorial

How to loop through all the files in a directory in c .net?

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Looping through files is a routine task in .NET applications that import data, clean directories, generate reports, or process media. The main design choice is whether you want a full list of files up front or a streaming iterator that starts yielding results immediately. In most real programs, Directory.EnumerateFiles is the safer default because it handles large folders better than Directory.GetFiles.

Choose the Right Enumeration API

The simplest API is Directory.GetFiles, which returns a full array of file paths:

csharp
1using System;
2using System.IO;
3
4string folder = @"C:\Data\Input";
5
6foreach (string file in Directory.GetFiles(folder))
7{
8    Console.WriteLine(file);
9}

This is fine for small folders, but it builds the complete list before your loop starts. That means higher memory usage and slower time to first result if the directory is large.

For scalable traversal, prefer Directory.EnumerateFiles:

csharp
1using System;
2using System.IO;
3
4string folder = @"C:\Data\Input";
5
6foreach (string file in Directory.EnumerateFiles(folder))
7{
8    Console.WriteLine(file);
9}

This version streams file names lazily, which is usually what you want in batch jobs and CLI tools.

Real file processing rarely wants every file. Usually you want a subset such as .csv or .json, and often you want subdirectories too.

csharp
1using System;
2using System.IO;
3
4string folder = @"C:\Data\Input";
5
6foreach (string file in Directory.EnumerateFiles(
7    folder,
8    "*.csv",
9    SearchOption.AllDirectories))
10{
11    Console.WriteLine(file);
12}

If you need several extensions, enumerate broadly and filter in code:

csharp
1using System;
2using System.Collections.Generic;
3using System.IO;
4
5string folder = @"C:\Data\Input";
6var allowed = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
7{
8    ".csv",
9    ".txt",
10    ".json"
11};
12
13foreach (string file in Directory.EnumerateFiles(folder, "*", SearchOption.AllDirectories))
14{
15    if (allowed.Contains(Path.GetExtension(file)))
16    {
17        Console.WriteLine(file);
18    }
19}

That approach is clear and keeps extension handling explicit.

Handle Errors Without Killing the Whole Job

The happy path is easy. The real work is surviving invalid paths, permission issues, and locked files. If your program scans a large tree, one bad folder should not necessarily stop the entire run.

csharp
1using System;
2using System.IO;
3
4static void ProcessFile(string path)
5{
6    Console.WriteLine($"Processing: {path}");
7}
8
9string root = @"C:\Data\Input";
10
11try
12{
13    foreach (string file in Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories))
14    {
15        try
16        {
17            ProcessFile(file);
18        }
19        catch (IOException ex)
20        {
21            Console.WriteLine($"I/O problem for {file}: {ex.Message}");
22        }
23    }
24}
25catch (UnauthorizedAccessException ex)
26{
27    Console.WriteLine($"Access denied while scanning root: {ex.Message}");
28}
29catch (DirectoryNotFoundException ex)
30{
31    Console.WriteLine($"Directory missing: {ex.Message}");
32}

This split between scan-level and file-level exceptions makes long-running jobs much more resilient.

Use DirectoryInfo When Metadata Matters

If you need file sizes or timestamps, DirectoryInfo and FileInfo can make the code cleaner:

csharp
1using System;
2using System.IO;
3
4var dir = new DirectoryInfo(@"C:\Data\Input");
5
6foreach (FileInfo file in dir.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
7{
8    Console.WriteLine($"{file.Name} | {file.Length} bytes | {file.LastWriteTimeUtc:u}");
9}

This is useful when traversal and metadata reporting are part of the same workflow. If you only need path strings, Directory.EnumerateFiles is still simpler.

Add Async Processing Carefully

Enumeration itself is synchronous, but file processing can be asynchronous. The main trap is launching too many tasks at once. Use throttling if there are many files.

csharp
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Threading;
5using System.Threading.Tasks;
6
7static async Task ProcessFileAsync(string path)
8{
9    string text = await File.ReadAllTextAsync(path);
10    Console.WriteLine($"{Path.GetFileName(path)}: {text.Length} chars");
11}
12
13static async Task ProcessAllAsync(string folder)
14{
15    using var gate = new SemaphoreSlim(4);
16    var tasks = new List<Task>();
17
18    foreach (string file in Directory.EnumerateFiles(folder, "*.txt"))
19    {
20        await gate.WaitAsync();
21        tasks.Add(Task.Run(async () =>
22        {
23            try
24            {
25                await ProcessFileAsync(file);
26            }
27            finally
28            {
29                gate.Release();
30            }
31        }));
32    }
33
34    await Task.WhenAll(tasks);
35}

That pattern keeps throughput reasonable without opening hundreds of files at once.

Common Pitfalls

  • Using GetFiles on very large directories and paying unnecessary memory cost before processing starts.
  • Recursing through subdirectories without handling permission failures.
  • Filtering extensions with case-sensitive checks and silently skipping valid files.
  • Starting unbounded asynchronous file work and exhausting handles or disk throughput.
  • Hardcoding path separators instead of relying on Path helpers for cross-platform code.

Summary

  • 'Directory.EnumerateFiles is usually the best default for looping through files in .NET.'
  • Add search patterns and SearchOption.AllDirectories only when the job actually needs them.
  • Separate traversal errors from per-file processing errors.
  • Use DirectoryInfo when file metadata is part of the task.
  • If processing is asynchronous, throttle it so enumeration stays efficient and predictable.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms