[c#] Searching for file in directories recursively

I have the following code to recursively search for files through a directory, which returns a list of all xml files to me. All works well, except that xml files in the root directory are not included in the list.

I understand why, since the first thing it does is get the directories in the root, then get files, thus missing the GetFiles() call on the root. I tried including the GetFiles() call prior to the foreach, but the results are not as I expect.

public static ArrayList DirSearch(string sDir)
{
    try
    {
        foreach (string d in Directory.GetDirectories(sDir))
        {
            foreach (string f in Directory.GetFiles(d, "*.xml"))
            {
                string extension = Path.GetExtension(f);
                if (extension != null && (extension.Equals(".xml")))
                {
                fileList.Add(f);
                }
            }
            DirSearch(d);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
    return fileList;
}

My directory structure is something like:

RootDirectory
        test1.0.xml
            test1.1.xml
            test1.2.xml
  2ndLevDir
            test2.0.xml
            test2.1.xml
  3rdLevDir
               test3.0.xml
               test3.1.xml

Code returns:

test2.0.xml
test2.1.xml
test3.0.xml
test3.1.xml

I would like to return every file including:

test1.0.xml
test1.1.xml
test1.2.xml

Not very well verse with recursion. Any pointers would be greatly appreciated.

This question is related to c# recursion

The answer is


You will want to move the loop for the files outside of the loop for the folders. In addition you will need to pass the data structure holding the collection of files to each call of the method. That way all files go into a single list.

public static List<string> DirSearch(string sDir, List<string> files)
{
  foreach (string f in Directory.GetFiles(sDir, "*.xml"))
  {
    string extension = Path.GetExtension(f);
    if (extension != null && (extension.Equals(".xml")))
    {
      files.Add(f);
    }
  }
  foreach (string d in Directory.GetDirectories(sDir))
  {
    DirSearch(d, files);
  }
  return files;
}

Then call it like this.

List<string> files = DirSearch("c:\foo", new List<string>());

Update:

Well unbeknownst to me, until I read the other answer anyway, there is already a builtin mechanism for doing this. I will leave my answer in case you are interested in seeing how your code needs to be modified to make it work.


you can do something like this:

foreach (var file in Directory.GetFiles(MyFolder, "*.xml", SearchOption.AllDirectories))
        {
            // do something with this file
        }

You should have the loop over the files either before or after the loop over the directories, but not nested inside it as you have done.

foreach (string f in Directory.GetFiles(d, "*.xml"))
{
    string extension = Path.GetExtension(f);
    if (extension != null && (extension.Equals(".xml")))
    {
        fileList.Add(f);
    }
} 

foreach (string d in Directory.GetDirectories(sDir))
{
    DirSearch(d);
}

I tried some of the other solutions listed here, but during unit testing the code would throw exceptions I wanted to ignore. I ended up creating the following recursive search method that will ignore certain exceptions like PathTooLongException and UnauthorizedAccessException.

    private IEnumerable<string> RecursiveFileSearch(string path, string pattern, ICollection<string> filePathCollector = null)
    {
        try
        {
            filePathCollector = filePathCollector ?? new LinkedList<string>();

            var matchingFilePaths = Directory.GetFiles(path, pattern);

            foreach(var matchingFile in matchingFilePaths)
            {
                filePathCollector.Add(matchingFile);
            }

            var subDirectories = Directory.EnumerateDirectories(path);

            foreach (var subDirectory in subDirectories)
            {
                RecursiveFileSearch(subDirectory, pattern, filePathCollector);
            }

            return filePathCollector;
        }
        catch (Exception error)
        {
            bool isIgnorableError = error is PathTooLongException ||
                error is UnauthorizedAccessException;

            if (isIgnorableError)
            {
                return Enumerable.Empty<string>();
            }

            throw error;
        }
    }

Try the following method:

public static IEnumerable<string> GetXMLFiles(string directory)
{
    List<string> files = new List<string>();

    try
    {
        files.AddRange(Directory.GetFiles(directory, "*.xml", SearchOption.AllDirectories));
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }

    return files;
}

Using EnumerateFiles to get files in nested directories. Use AllDirectories to recurse throught directories.

using System;
using System.IO;

class Program
{
    static void Main()
    {
    // Call EnumerateFiles in a foreach-loop.
    foreach (string file in Directory.EnumerateFiles(@"c:\files",
        "*.xml",
        SearchOption.AllDirectories))
    {
        // Display file path.
        Console.WriteLine(file);
    }
    }
}

You are creating three lists, instead of using one (you don't use the return value of DirSearch(d)). You can use a list as a parameter to save the state:

static void Main(string[] args)
{
  var list = new List<string>();
  DirSearch(list, ".");

  foreach (var file in list)
  {
    Console.WriteLine(file);
  }
}

public static void DirSearch(List<string> files, string startDirectory)
{
  try
  {
    foreach (string file in Directory.GetFiles(startDirectory, "*.*"))
    {
      string extension = Path.GetExtension(file);

      if (extension != null)
      {
        files.Add(file);
      }
    }

    foreach (string directory in Directory.GetDirectories(startDirectory))
    {
      DirSearch(files, directory);
    }
  }
  catch (System.Exception e)
  {
    Console.WriteLine(e.Message);
  }
}

This returns all xml-files recursively :

var allFiles = Directory.GetFiles(path, "*.xml", SearchOption.AllDirectories);

For file and directory search purpose I would want to offer use specialized multithreading .NET library that possess a wide search opportunities and works very fast.

All information about library you can find on GitHub: https://github.com/VladPVS/FastSearchLibrary

If you want to download it you can do it here: https://github.com/VladPVS/FastSearchLibrary/releases

If you have any questions please ask them.

It is one demonstrative example how you can use it:

class Searcher
{
    private static object locker = new object(); 

    private FileSearcher searcher;

    List<FileInfo> files;

    public Searcher()
    {
        files = new List<FileInfo>(); // create list that will contain search result
    }

    public void Startsearch()
    {
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        // create tokenSource to get stop search process possibility

        searcher = new FileSearcher(@"C:\", (f) =>
        {
            return Regex.IsMatch(f.Name, @".*[Dd]ragon.*.jpg$");
        }, tokenSource);  // give tokenSource in constructor


        searcher.FilesFound += (sender, arg) => // subscribe on FilesFound event
        {
            lock (locker) // using a lock is obligatorily
            {
                arg.Files.ForEach((f) =>
                {
                    files.Add(f); // add the next received file to the search results list
                    Console.WriteLine($"File location: {f.FullName}, \nCreation.Time: {f.CreationTime}");
                });

                if (files.Count >= 10) // one can choose any stopping condition
                    searcher.StopSearch();
            }
        };

        searcher.SearchCompleted += (sender, arg) => // subscribe on SearchCompleted event
        {
            if (arg.IsCanceled) // check whether StopSearch() called
                Console.WriteLine("Search stopped.");
            else
                Console.WriteLine("Search completed.");

            Console.WriteLine($"Quantity of files: {files.Count}"); // show amount of finding files
        };

        searcher.StartSearchAsync();
        // start search process as an asynchronous operation that doesn't block the called thread
    }
}