Hi, I'm Ray

09 May, 2020

C#: Get files in directories (done wrong three ways)

Programming, .NET, C#

This is another one of those operations that you’ll find yourself doing at some point in your job, viz. getting a list files from a directory and its sub-directories. This should be easy. This should be one line of code.

This is how to get-files wrong in three different ways.

The job is to retrieve a list of all accessible files inside the “C:\Windows” directory and its sub-directories.

Let’s get started.
 

0. Preparation

First we’re going to prepare two methods for checking if a file or folder is accessible. We’ll use these for all the various methods.
You’ll have to extend some trust that these work.


protected bool isFileAccessible(string filename)
{
  if (string.IsNullOrWhiteSpace(filename))
  {
    return false;
  }

  if (!File.Exists(filename))
  {
    return false;
  }

  try
  {
    File.GetAccessControl(filename, System.Security.AccessControl.AccessControlSections.Access);
  }
  catch
  {
    return false;
  }

  return true;
}

protected bool isDirectoryAccessible(string path)
{
  if (string.IsNullOrWhiteSpace(path))
  {
    return false;
  }

  if (!Directory.Exists(path))
  {
    return false;
  }

  try
  {
    Directory.GetAccessControl(path, System.Security.AccessControl.AccessControlSections.Access);
  }
  catch
  {
    return false;
  }

  return true;
}

 

1. Easy one-liner

This is the easy one-liner that should work a treat.


using System.IO;

string[] fileList = Directory.GetFiles(@"C:\Windows", "*", SearchOption.AllDirectories);

However, the first inaccessible directory is going to cause it to throw an exception. We could use a try-catch block but as the method is high-level, we’ll get no results.


System.UnauthorizedAccessException: 'Access to the path 'C:\Windows\CSC' is denied.'

 

2. Classic recursion

You may remember that traversing a file system is the classic scenario for teaching recursion. That is when a function/method is called inside of itself.

We’ll pass the file results back using a reference because that seems to be the C# way.


public List<string> GetFiles(string path, string pattern)
{
  List<string> rs = new List<string>();

  findFiles_InDirectory(rs, path, pattern);

  return rs;
}

protected void findFiles_InDirectory(List<string> rs, string path, string pattern)
{
  foreach (string item in Directory.EnumerateFiles(path, pattern, SearchOption.TopDirectoryOnly))
  {
    if (!isFileAccessible(item))
    {
      continue;
    }

    rs.Add(item);
  }

  foreach (string item in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly))
  {
    if (!isDirectoryAccessible(item))
    {
      continue;
    }

    findFiles_InDirectory(rs, path, pattern);
  }
}

List<string> fileList = GetFiles(@"C:\Windows", "*");

This looks like it should work but then freaky-error. Is there too much recursion? Does C# not like recursion? Maybe it’s the reference.


System.StackOverflowException

 

3. Even more classic recursion
I’ll be honest, I’m not sure why method 2 caused a stack overflow error.

I decided to try recusion again with returns because the thought that C# couldn’t handle recursion was frankly crazy-talk.


public List<string> GetFiles(string path, string pattern)
{
  List<string> rs = new List<string>();

  rs.AddRange(findFiles_InDirectory(path, pattern));

  return rs;
}

protected List<string> findFiles_InDirectory(string path, string pattern)
{
  List<string> rs = new List<string>();

  foreach (string item in Directory.EnumerateFiles(path, pattern, SearchOption.TopDirectoryOnly))
  {
    if (!isFileAccessible(item))
    {
      continue;
    }

    rs.Add(item);
  }

  foreach (string item in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly))
  {
    if (!isDirectoryAccessible(item))
    {
      continue;
    }

    rs.AddRange(findFiles_InDirectory(path, pattern));
  }

  return rs;
}

List<string> fileList = GetFiles(@"C:\Windows", "*");

Yeah. This didn’t make any difference and by this point I was getting irked at all of my failures.


System.StackOverflowException

 

4. Queue (Yes!)
This was my final idea to use a queue and a loop. I was out of ideas.


public List<string> GetFiles(string path, string pattern)
{
  List<string> fileList = new List<string>();
  List<string> directoryList = new List<string>();

  directoryList.Add(path);

  while (true)
  {
    if (directoryList.Count <= 0)
    {
      break;
    }

    string directory = directoryList.First();
    directoryList.RemoveAt(0);

    if (!isDirectoryAccessible(directory))
    {
      continue;
    }

    foreach (string item in Directory.EnumerateDirectories(directory, "*", SearchOption.TopDirectoryOnly))
    {
      if (!isDirectoryAccessible(item))
      {
        continue;
      }

      directoryList.Add(item);
    }

    foreach (string item in Directory.EnumerateFiles(directory, pattern, SearchOption.TopDirectoryOnly))
    {
      if (!isFileAccessible(item))
      {
        continue;
      }

      fileList.Add(item);
    }
  }

  return fileList;
}

List<string> fileList = GetFiles(@"C:\Windows", "*");

 

I can’t explain why this task was so much more painful than I expected. Perhaps I overlooked something obvious?

Regardless, I hope someone finds this useful.

Cheers.