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.
Posted on Sat 9th May 2020
Modified on Sun 13th Mar 2022