Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to ignore an exception and continue processing a foreach loop?

Tags:

c#

.net

foreach

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);
}
like image 327
Barry Dysert Avatar asked Oct 03 '22 14:10

Barry Dysert


3 Answers

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.

like image 126
David S. Avatar answered Oct 19 '22 00:10

David S.


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.

like image 37
Servy Avatar answered Oct 19 '22 01:10

Servy


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.

like image 1
terrybozzio Avatar answered Oct 18 '22 23:10

terrybozzio