如何忽略异常并继续处理 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);
}
问题是IEnumerable<string>
表现得懒惰,(有点像溪流)。 foreach
从 files
中获取一个接一个的字符串,因此只有当它请求问题目录时,枚举器才会崩溃。(您可以通过调用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)
{
}
}
}