Darren Rose
Darren Rose

Reputation: 179

Custom directory enumeration using System.IO.Enumeration in .NET Core

The author of this post mentioned there is a newer API for quicker method of file and directory enumeration. But though the article seems to refer to examples, they are not present and I can't find any examples elsewhere on Google that use the new methods mentioned?

Does anyone have some example code that could enumerate a given folder and all sub-folders / files to return information such as size and last accessed etc?

Upvotes: 6

Views: 2624

Answers (1)

John H
John H

Reputation: 14630

I can see why you found it difficult to figure this out - there isn't much information around, and so I ended up digging around the source code to figure things out. For the following examples, I'm using this directory structure:

D:\Root\
|- Child\
|  |- Grandchild\
|  |  |- Grandchild.jpg
|  |  |- Grandchild.txt
|  |- Child.html
|  |- Child.txt
|- Root.txt

and importing the following namespaces:

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Enumeration;

Example 1: Listing all files and subdirectories, recursively

var enumeration = new FileSystemEnumerable<FileSystemInfo>(
    @"D:\Root",
    // What's returned here has to match the generic type
    // passed in when creating the FileSystemEnumerable.
    // As DirectoryInfo inherits from FileSystemInfo, we
    // can use FileSystemInfo for both directories and files.
    (ref FileSystemEntry entry) => entry.ToFileSystemInfo(),
    // Should be self-explanatory.
    new EnumerationOptions() { RecurseSubdirectories = true })
{
    // We want to include everything: all directories and files.
    ShouldIncludePredicate = (ref FileSystemEntry entry) => true
};

As FileSystemEnumerable<T> implements IEnumerable<T>, for our example it's a case of iterating over a collection of FileSystemInfo - there's nothing special here:

foreach (var item in enumeration)
{
    Console.WriteLine($"{item.FullName} - {item.LastAccessTime}");
}

which prints:

D:\Root\Child - 03/10/2020 18:34:16
D:\Root\Root.txt - 03/10/2020 17:39:54
D:\Root\Child\Child.html - 03/10/2020 18:27:20
D:\Root\Child\Child.txt - 03/10/2020 17:40:15
D:\Root\Child\Grandchild - 03/10/2020 18:44:36
D:\Root\Child\Grandchild\Grandchild.jpg - 03/10/2020 18:44:28
D:\Root\Child\Grandchild\Grandchild.txt - 03/10/2020 17:40:10

You'll notice a couple of things:

  1. Even when recursing, from the caller's perspective, it's a flat structure
  2. It appears results are ordered first by subdirectories in alphabetical order, and then by filenames in alphabetical order

Example 2: Listing all subdirectories, recursively

// We're returning strings this time.
var enumeration = new FileSystemEnumerable<string>(
    @"D:\Root",
    // Returns the full path to the directory.
    (ref FileSystemEntry entry) => entry.ToFullPath(),
    new EnumerationOptions() { RecurseSubdirectories = true })
{
    // Only include directories in the result set.
    ShouldIncludePredicate = (ref FileSystemEntry entry) => entry.IsDirectory
};

Running this:

foreach (var item in enumeration)
{
    Console.WriteLine(item);
}

prints:

D:\Root\Child
D:\Root\Child\Grandchild

Example 3: Filtering files by extension

var extensions = new List<string> { ".html", ".jpg" };

// Back to using FileSystemInfo.
var enumeration = new FileSystemEnumerable<FileSystemInfo>(
    @"D:\Root",
    (ref FileSystemEntry entry) => entry.ToFileSystemInfo(),
    new EnumerationOptions() { RecurseSubdirectories = true })
{
    ShouldIncludePredicate = (ref FileSystemEntry entry) =>
    {
        // Skip directories.
        if (entry.IsDirectory) 
        {
            return false;
        }
        
        foreach (string extension in extensions)
        {
            var fileExtension = Path.GetExtension(entry.FileName);
            if (fileExtension.EndsWith(extension, StringComparison.OrdinalIgnoreCase))
            {
                // Include the file if it matches one of our extensions.
                return true;
            }
        }

        // Doesn't match, so exclude it.
        return false;
    }
};

Running this:

foreach (var item in enumeration)
{
    Console.WriteLine(item.FullName);
}

prints:

D:\Root\Child\Child.html
D:\Root\Child\Grandchild\Grandchild.jpg

Example 4: Excluding some directories from recursion

I just found another delegate, which controls the recursive behaviour. Let's say we modify the original directory structure to include a new directory with files that we want to ignore:

D:\Root\
|- Child\
|  |- Grandchild\
|  |  |- Grandchild.jpg
|  |  |- Grandchild.txt
|  |- GrandchildIgnore\
|  |  |- GrandchildIgnore.txt
|  |- Child.html
|  |- Child.txt
|- Root.txt

To get a list of all the other files, whilst ignoring files in our new directory, we can set the ShouldRecursePredicate:

var enumeration = new FileSystemEnumerable<FileSystemInfo>(
    @"D:\Root",
    (ref FileSystemEntry entry) => entry.ToFileSystemInfo(),
    new EnumerationOptions() { RecurseSubdirectories = true })
{
    ShouldIncludePredicate = (ref FileSystemEntry entry) => !entry.IsDirectory,
    // If the directory's name ends with ignore, skip it.
    ShouldRecursePredicate = (ref FileSystemEntry entry) => !entry.ToFullPath().EndsWith("Ignore", StringComparison.OrdinalIgnoreCase))
};

Running this:

foreach (var item in enumeration)
{
    Console.WriteLine(item.FullName);
}

prints:

D:\Root\Root.txt
D:\Root\Child\Child.html
D:\Root\Child\Child.txt
D:\Root\Child\Grandchild\Grandchild.jpg
D:\Root\Child\Grandchild\Grandchild.txt

excluding the directory, as we wanted.

Example 5: Getting the length of files

var enumeration = new FileSystemEnumerable<(long, string)>(
    @"D:\Root",
    (ref FileSystemEntry entry) => (entry.Length, entry.FileName.ToString()))
{
    ShouldIncludePredicate = (ref FileSystemEntry entry) => !entry.IsDirectory
};

Given 1 file in D:\Root that has 122 bytes, running this:

foreach (var (length, fileName) in enumeration)
{
    Console.WriteLine($"Name: {fileName} Length: {length}");
}

prints:

Name: Root.txt Length: 122

Upvotes: 10

Related Questions