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);
}
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With