.NET 4.0中有一个很好的新方法,可以通过枚举以流方式获取目录中的文件。
这里的问题是,如果想要枚举所有文件,可能事先不知道哪些文件或文件夹受到访问保护,并且可能抛出UnauthorizedAccessException。
要重现,可以运行此片段:
foreach (var file in Directory.EnumerateFiles(@"c:\", "*", SearchOption.AllDirectories))
{
// whatever
}
在此.NET方法存在之前,通过在字符串数组返回方法上实现递归迭代器,可以实现大致相同的效果。但它并不像新的.NET方法那么懒惰。
那该怎么办?使用此方法时,UnauthorizedAccessException可以被抑制还是生活中的事实?
在我看来,该方法应该有一个重载接受一个处理任何异常的动作。
答案 0 :(得分:26)
我无法完成上述工作,但这是我的实现,我已经在“Win7”盒子上的c:\ users上进行了测试,因为如果有所有这些“讨厌”的目录:
SafeWalk.EnumerateFiles(@"C:\users", "*.jpg", SearchOption.AllDirectories).Take(10)
类别:
public static class SafeWalk
{
public static IEnumerable<string> EnumerateFiles(string path, string searchPattern, SearchOption searchOpt)
{
try
{
var dirFiles = Enumerable.Empty<string>();
if(searchOpt == SearchOption.AllDirectories)
{
dirFiles = Directory.EnumerateDirectories(path)
.SelectMany(x => EnumerateFiles(x, searchPattern, searchOpt));
}
return dirFiles.Concat(Directory.EnumerateFiles(path, searchPattern));
}
catch(UnauthorizedAccessException ex)
{
return Enumerable.Empty<string>();
}
}
}
答案 1 :(得分:7)
上述答案的问题在于不处理子目录中的异常。这将是处理这些异常的更好方法,因此您可以从所有子目录获取所有文件,除了那些引发访问异常的文件:
/// <summary>
/// A safe way to get all the files in a directory and sub directory without crashing on UnauthorizedException or PathTooLongException
/// </summary>
/// <param name="rootPath">Starting directory</param>
/// <param name="patternMatch">Filename pattern match</param>
/// <param name="searchOption">Search subdirectories or only top level directory for files</param>
/// <returns>List of files</returns>
public static IEnumerable<string> GetDirectoryFiles(string rootPath, string patternMatch, SearchOption searchOption)
{
var foundFiles = Enumerable.Empty<string>();
if (searchOption == SearchOption.AllDirectories)
{
try
{
IEnumerable<string> subDirs = Directory.EnumerateDirectories(rootPath);
foreach (string dir in subDirs)
{
foundFiles = foundFiles.Concat(GetDirectoryFiles(dir, patternMatch, searchOption)); // Add files in subdirectories recursively to the list
}
}
catch (UnauthorizedAccessException) { }
catch (PathTooLongException) {}
}
try
{
foundFiles = foundFiles.Concat(Directory.EnumerateFiles(rootPath, patternMatch)); // Add files from the current directory
}
catch (UnauthorizedAccessException) { }
return foundFiles;
}
答案 2 :(得分:1)
我理解它引发异常MoveNext
。
我尝试编写一个安全遍历序列并尝试忽略MoveNext
异常的方法。但是我不确定MoveNext
在抛出异常时是否会提升位置,所以这也可能是无限循环。这也是一个坏主意,因为我们会依赖实现细节。
但它只是非常有趣!
public static IEnumerable<T> SafeWalk<T> (this IEnumerable<T> source)
{
var enumerator = source.GetEnumerator();
bool? hasCurrent = null;
do {
try {
hasCurrent = enumerator.MoveNext();
} catch {
hasCurrent = null; // we're not sure
}
if (hasCurrent ?? false) // if not sure, do not return value
yield return enumerator.Current;
} while (hasCurrent ?? true); // if not sure, continue walking
}
foreach (var file in Directory.EnumerateFiles("c:\\", "*", SearchOption.AllDirectories)
.SafeWalk())
{
// ...
}
仅当以下条件适用于此迭代器的框架实现时才会起作用(请参阅Reflector中的FileSystemEnumerableIterator<TSource>
以供参考):
MoveNext
在失败时提升其位置; MoveNext
在最后一个元素上失败时,后续调用将返回false
而不是抛出异常; 即使它有效,请不要在生产中使用它! 但我真的很想知道是不是。
答案 3 :(得分:0)
基于strudso的答案,但作为FileInfo
和DirectoryInfo
的扩展方法。
public static IEnumerable<FileInfo> EnumerateFilesSafe(this DirectoryInfo dir, string filter = "*.*", SearchOption opt = SearchOption.TopDirectoryOnly)
{
var retval = Enumerable.Empty<FileInfo>();
try { retval = dir.EnumerateFiles(filter); }
catch { Debug.WriteLine("{0} Inaccessable.", dir.FullName); }
if (opt == SearchOption.AllDirectories)
retval = retval.Concat(dir.EnumerateDirectoriesSafe(opt: opt).SelectMany(x => x.EnumerateFilesSafe(filter, opt)));
return retval;
}
public static IEnumerable<DirectoryInfo> EnumerateDirectoriesSafe(this DirectoryInfo dir, string filter = "*.*", SearchOption opt = SearchOption.TopDirectoryOnly)
{
var retval = Enumerable.Empty<DirectoryInfo>();
try { retval = dir.EnumerateDirectories(filter); }
catch { Debug.WriteLine("{0} Inaccessable.", dir.FullName); }
if (opt == SearchOption.AllDirectories)
retval = retval.Concat(retval.SelectMany(x => x.EnumerateDirectoriesSafe(filter, opt)));
return retval;
}
答案 4 :(得分:0)
我来晚了,但是我建议使用可观察模式:
public class FileUtil
{
private static void FindFiles_(string path, string pattern,
SearchOption option, IObserver<string> obs, CancellationToken token)
{
try
{
foreach (var file in Directory.EnumerateFiles(path, pattern,
SearchOption.TopDirectoryOnly))
{
if (token.IsCancellationRequested) break;
obs.OnNext(file);
}
if (option != SearchOption.AllDirectories) return;
foreach (var dir in Directory.EnumerateDirectories(path, "*",
SearchOption.TopDirectoryOnly))
{
FindFiles_(dir, pattern, option, obs, token);
}
}
catch (UnauthorizedAccessException) { }
catch (PathTooLongException) { }
catch (IOException) { }
catch (Exception err) { obs.OnError(err); }
}
public static IObservable<string> GetFiles(string root, string pattern,
SearchOption option)
{
return Observable.Create<string>(
(obs, token) =>
Task.Factory.StartNew(
() =>
{
FindFiles_(root, pattern, option, obs, token);
obs.OnCompleted();
},
token));
}
}
答案 5 :(得分:0)
我做了一个自己的实现该类的类的实现,因为以前的答案似乎并没有达到我想要的目的。这样只会跳过它无法访问的所有文件和文件夹,并返回它可以访问的所有文件。
public static class SafeWalk
{
public static IEnumerable<string> EnumerateFiles(string path, string searchPattern, SearchOption searchOpt)
{
if (searchOpt == SearchOption.TopDirectoryOnly)
{
return Directory.EnumerateFiles(path, searchPattern, SearchOption.TopDirectoryOnly);
}
List<string> folders = new List<string>() { path };
int folCount = 1;
List<string> files = new List<string>() { };
for (int i = 0; i < folCount; i++)
{
try
{
foreach (var newDir in Directory.EnumerateDirectories(folders[i], "*", SearchOption.TopDirectoryOnly))
{
folders.Add(newDir);
folCount++;
try
{
foreach (var file in Directory.EnumerateFiles(newDir, searchPattern))
{
files.Add(file);
}
} catch (UnauthorizedAccessException)
{
// Failed to read a File, skipping it.
}
}
}
catch (UnauthorizedAccessException)
{
// Failed to read a Folder, skipping it.
continue;
}
}
return files;
}
}
与常规的EnumerateFiles函数完全一样,仅使用SafeWalk.EnumerateFiles(...)而不是Dictionary.EnumerateFiles(...)
答案 6 :(得分:0)
将其发布为答案,因为我没有代表添加评论,更不用说编辑现有答案了。我的要求是最小化内存分配,冗余变量,并让系统对目录进行单个枚举。
static IEnumerable<string> FindFiles(string path, string filter = "*", bool recursive = false)
{
IEnumerator<string> fEnum;
try
{
fEnum = Directory.EnumerateFiles(path, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly).GetEnumerator();
}
catch (UnauthorizedAccessException) { yield break; }
while (true)
{
try { if (!fEnum.MoveNext()) break; }
catch (UnauthorizedAccessException) { continue; }
yield return fEnum.Current;
}
}
Dan Bechard在评论中提到:
不幸的是,MoveNext()在引发异常时不会提高位置。
这可能已在更高版本的.Net或Windows 10版本中得到解决? Windows 10的.NET 5.0中没有这个问题。通过搜索整个系统驱动器进行了测试。