6

I have a test program that is supposed to loop over all the files under C:. It dies when it hits the "Documents and Settings" folder. I'd like to ignore the error and keep looping over the remaining files. The problem is that the exception is thrown in the foreach, so putting a try/catch around the foreach will cause the loop to exit. And putting a try/catch after the foreach never fires (because the exception is thrown in the foreach). Is there any way to ignore the exception and continue processing the loop?

Here's the code:

static void Main(string[] args)
{
    IEnumerable<string> files = Directory.EnumerateFiles(@"C:\", "*.*",
                                SearchOption.AllDirectories);
    foreach (string file in files)  // Exception occurs when evaluating "file"
        Console.WriteLine(file);
}
Barry Dysert
  • 665
  • 1
  • 9
  • 21

4 Answers4

3

The problem is that IEnumerable<string> behaves lazily, (kind of like a stream). foreach takes one string after another from files, so only when it requests the problem directory, the enumerator crashes. (you can confirm this by calling ToList() or something like this:

//should now crash here instead
List<string> files = Directory.EnumerateFiles(@"C:\", "*.*",
                            SearchOption.AllDirectories).ToList();

I think you need to find a way to convert files into an enumerated collection, by manually getting each item from files and do a try/catch around the actual read from the enumerator.

edit: chris and servy has good points in comments. You probably shouldn't mess with the enumerator at all after it throws an exception. Try to be more conservative in your query of files would probably be the easiest solution.

David S.
  • 5,965
  • 2
  • 40
  • 77
  • Same problem. Once you hit an exception that enumerator is likely no longer in a state to be usable anymore. No different from wrapping the whole `foreach` loop. – Servy Oct 24 '13 at 15:54
  • @DavidD: Well done on giving a better and more correct answer than me. ;-) Getting the enumerator manually probably won't help because once its thrown an exception you probably don't want to be messing with it. The more sensible thing to do here is probably to make sure that you don't tell it to enumerate invalid files... – Chris Oct 24 '13 at 15:56
  • @Servy and Chris yeah good point. Probably the solution would be to do an actual work around by not requested every file on the drive. – David S. Oct 24 '13 at 15:58
  • 1
    @DavidS.: I suspect the best solution may be to write your own version that acts recursively and can then have try catches to skip permission errors inside. – Chris Oct 24 '13 at 16:00
  • @Chris agreed, that sounds like the most general and robust solution – David S. Oct 24 '13 at 16:01
3

There is no effective way for you to handle this case. If the iterator itself is throwing an exception when trying to get the next item then it is not going to be in a position to give you the item after that; it is no longer in a "valid" state. Once an exception is thrown you're done with that iterator.

Your only option here is to not query the entire drive using this method. Perhaps you could write your own iterator to traverse the drive manually in such a way that items that cannot be traversed are skipped more gracefully.

So, to do this manual traversal. We can start out with a generalize Traverse method that can traverse any tree (non-recursively, to better handle deep stacks efficiently):

public static IEnumerable<T> Traverse<T>(
    this IEnumerable<T> source
    , Func<T, IEnumerable<T>> childrenSelector)
{
    var stack = new Stack<T>(source);
    while (stack.Any())
    {
        var next = stack.Pop();
        yield return next;
        foreach (var child in childrenSelector(next))
            stack.Push(child);
    }
}

Now we just grab the root, traverse it, and use a method of getting the sub directories that won't throw an exception:

var root = new DirectoryInfo("C:\\");
var allFiles = new[] { root }.Traverse(dir => GetDirectoriesWithoutThrowing(dir))
    .SelectMany(dir => GetFilesWithoutThrowing(dir));

The method to get the sub directories can start out as something like this, but may need to be fleshed out:

private static IEnumerable<DirectoryInfo> GetDirectoriesWithoutThrowing(
    DirectoryInfo dir)
{
    try
    {
        return dir.GetDirectories();
    }
    catch (Exception)//if possible catch a more derived exception
    {
        //TODO consider logging the exception
        return Enumerable.Empty<DirectoryInfo>();
    }
}

Note that you can play around with this particular part a bit. You may be able to get some of the sub directories out here even if you can't get others, you'll likely need to adjust the error handling, etc. The get files version will be essentially the same, but just with GetFiles instead.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • As mentioned in general comments http://stackoverflow.com/questions/2559064/is-there-a-way-of-recover-from-an-exception-in-directory-enumeratefiles is basically the same question and has some code that pretty much does what you suggest. Thought I'd mention that more explicitly here since this seems the most likely accepted answers currently (and editing in the link might be good). – Chris Oct 24 '13 at 16:08
  • 1
    @Chris That solution isn't terrible, but it eagerly searches the whole tree, and isn't exactly as clean as it could be. I added in my own solution instead. – Servy Oct 24 '13 at 16:17
1

You are probably getting an UnauthorizedAccessException because some files are hidden or a system file.Since you are working with strings(from enumeratefiles)and not fileinfos or directoryinfo objects i rewrote this to fit your design:

first add these 2 member variables:

private static Dictionary<DirectoryInfo, List<string>> DirectoryTree = new Dictionary<DirectoryInfo, List<string>>();
private static List<string> logger = new List<string>();

then place this method:

static void RecursiveSearch(string root)
{
     string[] files = null;
     string[] subDirs = null;

     // First, process all the files directly under this folder 
     try
     {
        files = Directory.EnumerateFiles(root).ToArray();
     }
     catch (UnauthorizedAccessException e)
     {
         logger.Add(e.Message);
     }
     catch (System.IO.DirectoryNotFoundException e)
     {
         Console.WriteLine(e.Message);
     }

     if (files != null)
     {

          DirectoryTree.Add(new DirectoryInfo(root), files.ToList());
          subDirs = Directory.GetDirectories(root);

          foreach (string dir in subDirs)
          {
              RecursiveSearch(dir);
          }
     }
}

Then in Main you call it like so:

RecursiveSearch(@"c:\");

After your DirectoryTree dictionary and list logger will be filled.

terrybozzio
  • 4,424
  • 1
  • 19
  • 25
-3
static void Main(string[] args)
{
    IEnumerable<string> files = Directory.EnumerateFiles(@"C:\", "*.*",
                            SearchOption.AllDirectories);
    foreach (string file in files)  // Exception occurs when evaluating "file"
    {
        try
        {
            Console.WriteLine(file);
        }
        Catch(Exception ex)
        {
        }
    }
}
KF-SoftwareDev
  • 403
  • 1
  • 6
  • 17
  • 1
    It's not the console write that'll be throwing the exception here; it's the iterator. Also, empty catch-all catch blocks like this are *terrible* practice. – Servy Oct 24 '13 at 15:51