Enumerating Files and Directories in VB 2010

Introduction

Microsoft has called me a few times in the past for an interview. Once to work for the ASP.NET team and once to work for what used to be called the national practices services, part of Microsoft Consulting, and for a while I worked as a consultant for Microsoft consulting services.

Microsoft is building software for general consumption, or thinking of it another way, for the widest audience possible. Part of my interview of the ASP.NET was how to build software for general consumption. The challenge with general consumption software is that a developer may want to make things easier, but easy software sometimes opens some doors and closes others. I think this is what happened with Directory.GetFiles and Directory.GetDirectories.

GetFiles and GetDirectories replaced the API methods findfirst and findnext to make traversing the file system easier. The drawback with GetFiles and GetDirectories was that these methods returned arrays, and the code had to wait until the entire array was populated before the calls returned. To eliminate the wait GetFiles and GetDirectories have newer versions named EnumerateFiles and EnumerateDirectories. You no longer have to wait for EnumerateFiles and EnumerateDirectories to return to begin using the returned files and directories, but there are a couple of minor obstacles involved here to.

With EnumerateFiles and EnumerateDirectories you don’t have to map API calls, write your own recursive descent to map items in sub-folders, and you don’t have to wait for the entire enumeration to finish to interact with the results. The obstacle is that if you encounter an error, for example when you enumerate folders and child folders, the EnumerateXxx method will throw an exception, and the enumeration will fail. This makes it difficult to do something as seemingly simple as create a file system browser with these methods. There is hope though.

Let’s take a look at EnumerateFiles and EnumerateDirectories, how to use LINQ, and exception handlers to walk the file system even in the face of access exceptions.

Using EnumerateFiles and EnumerateDirectories

A version of EnumerateFiles and EnumerateDirectories exists in both the Directory and DirectoryInfo classes in the System.IO namespace. The version in the Directory class can traverse sub-directories and returns just the names of the matched items. If you need the file or directory information then use the DirectoryInfo version. Both EnumerateFiles and EnumerateDirectories have several overloaded versions that accept a variation of arguments including the starting path, the file mask, and a SearchOption enumeration argument that determines whether just the top folder or all folders are searched.

The following code uses Directory.EnumerateFiles to return the files in the root of the C: drive. The resultant enumeration is used as a source in a LINQ query. (In this case it is worth noting that the LINQ query doesn’t filter the data so it is superfluous; that is you could just use Directory.EnumeratoryFiles by itself.) The Array.ForEach method converts the enumeration to an array, a requirement of the ForEach method, and a Lambda expression to write the results to the console.

  Dim files = From file In Directory.EnumerateFiles("C:")
              Select file
  Array.ForEach(files.ToArray(), Sub(f) Console.WriteLine(f))

In such a simple use case EnumerateFiles and EnumerateDirectories are easy to use. This is what we want from a framework.

Handling EnumerateDirectories Exceptions

If you call Directory.EnumerateDirectories with the SearchOption.AllDirectories argument then EnumerateDirectories will perform a directory traversal for you, which includes sub-folders. However, if you start at a high level directory like the root then you are likely to encounter a folder that will cause an access exception. You won’t know this until you touch the directory data though. For example, if you write:

  Dim d = Directory.EnumerateDirectories("C:", "*.*", SearchOption.AllDirectories)

Then the code will compile and run. If you use the results as the source for a for loop such as the following:

  ' Get all directories
  For Each d In Directory.EnumerateDirectories("C:", "*.*", SearchOption.AllDirectories)
    Console.WriteLine(d)
  Next

Then the code may throw an UnauthorizedAccessException when it hits a folder like C:Documents and Settings. To catch the exception wrap the for loop in a Try Catch block. Here is the revised code.

  Try
    For Each d In Directory.EnumerateDirectories("C:", "*.*", SearchOption.AllDirectories)
      Console.WriteLine(d)
    Next
  Catch ex As Exception
    Console.WriteLine("Handle exception here")
  End Try

You can use a broader exception class like Exception to catch all possible exceptions or multiple catch blocks if you want to handle individual kinds of exceptions differently.

A potential challenge here is that if you actually want to display or capture all folders even if access is denied then you need a slightly different approach than simply using the SearchOption.AllDirectories argument.

Searching All Folders Without Failing on Exceptions

Suppose you want to traverse all folders. Suppose further that if you encounter an exception you want to skip that folder and continue. Until there is an option like SearchOption.ContinueOnErrors or an attachable event that performs a semantically similar operation there is no opportunity to continue on an exception when it only takes one method call to traverse sub-folders. For this reason you need to split the call up.

To effectively skip folders and continue you can combine a Stack object and obtain top level folders. Next, pop each folder off the stack and try to query the sub-folders, repeat the pushing and popping of the stack until all folders and sub-folders have been traversed. Because this approach splits the traversal into an outer loop to manage the stack you can handle an exception and continue processing the items in the stack. Listing 1 provides a solution that will grab unauthorized folders-for example for your file system management tool-too and continue processing other folders.

  Imports System.IO
  Imports System.Security

  Module Module1

      Sub Main()
        Dim results As List(Of String) = New List(Of String)
        Dim start As String = "c:"
        results.Add(start)
        Dim stack As Stack(Of String) = New Stack(Of String)

        Do
          Try
            Debug.WriteLine(start)

            Dim dirs = From d In Directory.EnumerateDirectories(start,
              "*.*", SearchOption.TopDirectoryOnly)
              Select d

            ' multline Lambda - don't really need this
            Array.ForEach(dirs.ToArray(), Sub(d)
              stack.Push(d)
            End Sub)

            start = stack.Pop()
            results.Add(start)

          Catch ex As UnauthorizedAccessException
            Console.WriteLine(ex.Message)
            start = stack.Pop()
            results.Add(start)
          End Try

        Loop Until (stack.Count = 0)

        For Each d In results
            Console.WriteLine(d)
        Next

        Console.ReadLine()

      End Sub
  End Module

Listing 1: Using a Stack to track top-level folders and then process sub-folders one at a time in the event of an exception.

The List(Of String) will contain the found directories. The variable start will start the search at the root of the C: drive. The stack is used to store the directories that need to be searched. The Do loop starts the process and the Try begins the exception handling block.

The statement beginning with Dim dirs begins searching the sub-folders in the folder assigned to start. SearchOption.TopDirectoryOnly will return the folders in start. (You could skip the LINQ query if you didn’t want to perform an additional filtering, but the code does demonstrate how to incorporate LINQ if you need to.) The Array.ForEach pushes all of the child folders on the stack using multi-line Lambda sub-expression syntax. Next, the stack is popped, the next folder is stored in the results list, and the process continues if the stack is not empty. If an exception is thrown the stack is popped again and the next item is stored.

The way this solution is written and by starting at the root this will be a long running process. However, since the solution is broken up into multiple stages you have plenty of opportunities to interact with the results along the way and you can even split the solution into a background process–which is probably what Windows Explorer does.

Summary

EnumerateFiles and EnumerateDirectories make it really easy to get file and directory names and information. The most common complaint on the web is that these methods stop processing on an exception. By splitting the call up by directories you can manage exceptions in an outer loop. The online response to aborted processing is to modify the EnumerateXxx methods to incorporate continue behavior.

Related Articles

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read