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

本文关键字:处理 foreach 循环 继续 何忽略 异常 | 更新日期: 2023-09-27 18:36:41

我有一个测试程序,应该循环遍历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);
}

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

问题是IEnumerable<string>表现得懒惰,(有点像溪流)。 foreachfiles 中获取一个接一个的字符串,因此只有当它请求问题目录时,枚举器才会崩溃。(您可以通过调用ToList()或类似的方式来确认这一点:

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

我认为您需要找到一种方法将files转换为枚举集合,方法是手动从files获取每个项目,并在枚举器的实际读取周围进行尝试/捕获。

编辑:克里斯和塞尔维在评论中有很好的观点。在枚举器引发异常后,您可能根本不应该弄乱枚举器。尝试在查询文件时更加保守可能是最简单的解决方案。

没有有效的方法来处理这种情况。 如果迭代器本身在尝试获取下一项时抛出异常,那么它将无法在此之后为您提供该项目;它不再处于"有效"状态。 引发异常后,您就完成了该迭代器。

此处唯一的选择是不使用此方法查询整个驱动器。 也许您可以编写自己的迭代器来手动遍历驱动器,以便更优雅地跳过无法遍历的项目。

因此,要进行此手动遍历。 我们可以从一个可以遍历任何树的通用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

您可能会收到 UnauthorizedAccessException,因为某些文件是隐藏的或系统文件。由于您正在使用字符串(来自枚举文件)而不是 fileinfo 或目录信息对象,因此我重写了它以适合您的设计:

首先添加以下 2 个成员变量:

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

然后在 Main 中这样称呼它:

RecursiveSearch(@"c:'");

之后您的目录树词典和列表记录器将被填满。

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