C#
Directory.EnumerateFiles
file filtering
programming
.NET

How to filter Directory.EnumerateFiles with multiple criteria?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Directory.EnumerateFiles only gives you one built-in search pattern, but you can layer additional conditions on top of it with LINQ or plain if checks. That is the normal way to handle “multiple criteria” in C#: enumerate lazily, inspect each file, and keep only the ones that satisfy all of your rules.

Start With Enumeration, Then Add Filters

Directory.EnumerateFiles is useful because it streams results instead of loading every file path into memory at once. That makes it a better starting point than Directory.GetFiles when you are scanning large directory trees.

A straightforward example:

csharp
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Linq;
5
6public static class FileSearch
7{
8    public static IEnumerable<string> FindMatchingFiles(string root)
9    {
10        var allowedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
11        {
12            ".log",
13            ".txt"
14        };
15
16        var cutoff = DateTime.UtcNow.AddDays(-7);
17
18        return Directory.EnumerateFiles(
19                root,
20                "*",
21                new EnumerationOptions
22                {
23                    RecurseSubdirectories = true,
24                    IgnoreInaccessible = true,
25                    ReturnSpecialDirectories = false
26                })
27            .Select(path => new FileInfo(path))
28            .Where(file => allowedExtensions.Contains(file.Extension))
29            .Where(file => file.Length > 10_000)
30            .Where(file => file.LastWriteTimeUtc >= cutoff)
31            .Where(file => file.Name.Contains("error", StringComparison.OrdinalIgnoreCase))
32            .Select(file => file.FullName);
33    }
34}

That method applies four criteria:

  • extension must be .log or .txt
  • file size must be greater than 10_000 bytes
  • last write time must be within the last seven days
  • file name must contain error

Because the query stays lazy, the filtering happens as you enumerate the results.

Narrow Early When You Can

The searchPattern argument can still help reduce work before your custom filters run. If you only care about one extension, use it:

csharp
var recentLogs = Directory.EnumerateFiles(root, "*.log")
    .Where(path => File.GetLastWriteTimeUtc(path) >= DateTime.UtcNow.AddDays(-1));

If you need several extensions, you usually switch back to "*" and then filter with Path.GetExtension, FileInfo.Extension, or a lookup set as shown earlier.

Using Plain Loops Instead of LINQ

LINQ is concise, but a plain iterator method can be easier to debug and lets you catch exceptions around file access in one place.

csharp
1using System;
2using System.Collections.Generic;
3using System.IO;
4
5public static IEnumerable<string> FindLargeCsvFiles(string root)
6{
7    foreach (var path in Directory.EnumerateFiles(root, "*.csv", SearchOption.AllDirectories))
8    {
9        FileInfo file;
10
11        try
12        {
13            file = new FileInfo(path);
14        }
15        catch (IOException)
16        {
17            continue;
18        }
19        catch (UnauthorizedAccessException)
20        {
21            continue;
22        }
23
24        if (file.Length < 5_000_000)
25        {
26            continue;
27        }
28
29        if (file.LastWriteTimeUtc < DateTime.UtcNow.AddMonths(-1))
30        {
31            continue;
32        }
33
34        yield return file.FullName;
35    }
36}

This style is especially useful when file metadata access may fail or when you need more complex branching logic.

Performance Considerations

The biggest performance win is usually avoiding unnecessary work. A few guidelines help:

  • use the most selective searchPattern you can
  • avoid creating expensive objects multiple times for the same path
  • keep the query lazy so you do not materialize huge file lists too early
  • be careful with deep recursion into large directory trees

If you only need file paths and one lightweight check, you may not need FileInfo at all. Path.GetExtension(path) plus a few File API calls can be enough.

Common Pitfalls

One common mistake is expecting Directory.EnumerateFiles to accept several wildcard patterns in one call. It does not. You get one pattern, and everything else must be handled in your own filtering logic.

Another issue is access failures. Directory scans often cross into folders your process cannot read. Use EnumerationOptions.IgnoreInaccessible where available or catch UnauthorizedAccessException.

Case sensitivity can also surprise you. File extension comparisons should usually use StringComparer.OrdinalIgnoreCase, especially on systems where users do not consistently name files with the same casing.

Finally, do not switch to GetFiles unless you actually need the full list immediately. EnumerateFiles is usually the safer default for large scans because it keeps memory pressure lower.

Summary

  • 'Directory.EnumerateFiles is the right starting point for lazy file scanning.'
  • Combine it with LINQ or iterator logic to enforce multiple conditions.
  • Use the built-in searchPattern to narrow the scan when possible.
  • Handle inaccessible files and metadata lookups defensively.
  • Prefer lazy enumeration over loading every path into memory up front.

Course illustration
Course illustration

All Rights Reserved.