如何忽略异常并继续处理foreach循环?

6
我有一个测试程序,它应该循环遍历C:下的所有文件。当它到达“文档和设置”文件夹时,它会停止运行。我想忽略这个错误并继续循环处理其余的文件。问题在于异常是在foreach中抛出的,因此在foreach周围放置try/catch将导致循环退出。而在foreach之后放置try/catch从来不会触发(因为异常是在foreach中抛出的)。有没有办法忽略异常并继续处理循环?
以下是代码:
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);
}

这可以帮助你,http://msdn.microsoft.com/zh-cn/library/dd997370.aspx - terrybozzio
1
无论你做什么,一定要捕获特定的异常而不是仅仅使用“Exception”。 - Brian Rasmussen
2
这里有一个类似的问题:https://dev59.com/JkzSa4cB1Zd3GeqPqu2i - Anton Sizikov
4个回答

3
没有有效的方法可以处理这种情况。如果迭代器在尝试获取下一个项时本身抛出异常,那么它将无法为您提供该项之后的内容;它不再处于“有效”状态。一旦抛出异常,您就无法继续使用该迭代器。
您唯一的选择是不使用此方法查询整个驱动器。也许您可以编写自己的迭代器以手动遍历驱动器,在这种方式下无法遍历的项更加优雅地被跳过。
因此,要进行手动遍历。我们可以从一个通用的Traverse方法开始,该方法可以遍历任何树(非递归地,以更好地高效处理深度堆栈):
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);
    }
}

现在我们只需获取根目录,遍历它,并使用一种不会抛出异常的方法来获取子目录:
var root = new DirectoryInfo("C:\\");
var allFiles = new[] { root }.Traverse(dir => GetDirectoriesWithoutThrowing(dir))
    .SelectMany(dir => GetFilesWithoutThrowing(dir));

获取子目录的方法可以从以下方式开始,但可能需要进行补充:

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>();
    }
}

请注意,您可以在这个特定部分上玩弄一下。即使您无法获取其他子目录,您可能仍然能够在这里获得其中一些,您可能需要调整错误处理等操作。获取文件的版本将基本相同,但只需使用 GetFiles 即可。

如一般评论中所提到的https://dev59.com/JkzSa4cB1Zd3GeqPqu2i基本上是相同的问题,并且有一些代码几乎可以做到您建议的事情。我想在这里更明确地提到,因为这似乎是目前最可能被接受的答案(并且编辑链接可能很好)。 - Chris
1
@Chris 这个解决方案并不糟糕,但是它会急切地搜索整个树,而且可能不够简洁。我加入了自己的解决方案。 - Servy

3
问题是 IEnumerable<string> 表现出了惰性(有点像流)。foreachfiles 中一个接着一个地取出字符串,所以只有在请求问题目录时,枚举器才会崩溃。(您可以通过调用 ToList() 或类似方法来确认这一点:)
//should now crash here instead
List<string> files = Directory.EnumerateFiles(@"C:\", "*.*",
                            SearchOption.AllDirectories).ToList();

我认为您需要找到一种方法将files转换为枚举集合,通过手动从files获取每个项目并在枚举器实际读取时使用try/catch。 编辑: 克里斯和塞维在评论中提出了很好的观点。在枚举器抛出异常后,您可能根本不应该干扰枚举器。尝试更保守地查询文件可能是最简单的解决方案。

同样的问题。一旦你遇到一个异常,枚举器很可能不再处于可用状态。这与将整个foreach循环包装起来没有什么不同。 - Servy
@DavidD:做得好,你的回答比我更好、更正确。;-) 手动获取枚举器可能没有帮助,因为一旦它抛出异常,你可能不想再去处理它了。在这里更明智的做法可能是确保你不要告诉它去枚举无效的文件... - Chris
@Servy和Chris,是的,好主意。也许解决方案是通过不请求驱动器上的每个文件来实际解决问题。 - David S.
1
@DavidS.:我怀疑最好的解决方案可能是编写自己的版本,该版本可以递归地执行,并且可以使用try catch跳过内部的权限错误。 - Chris
@Chris同意,那听起来像是最通用、最健壮的解决方案。 - David S.

1
您可能会收到UnauthorizedAccessException的提示,因为某些文件被隐藏或者是系统文件。由于您正在使用字符串(从enumeratefiles)而不是fileinfos或directoryinfo对象,我已经重写了以下内容以适应您的设计:

首先添加这两个成员变量:

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

然后放置这个方法:
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);
          }
     }
}

然后在主函数中这样调用它:
RecursiveSearch(@"c:\");

在您的DirectoryTree字典和列表记录器填充后。

-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)
        {
        }
    }
}

1
这里会抛出异常的不是控制台写入,而是迭代器。此外,像这样的空catch-all catch块是糟糕的实践。 - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接