Directory.EnumerateFiles => UnauthorizedAccessException

时间:2011-02-23 22:26:59

标签: .net filesystems lazy-evaluation unauthorizedaccessexcepti

.NET 4.0中有一个很好的新方法,可以通过枚举以流方式获取目录中的文件。

这里的问题是,如果想要枚举所有文件,可能事先不知道哪些文件或文件夹受到访问保护,并且可能抛出UnauthorizedAccessException。

要重现,可以运行此片段:

foreach (var file in Directory.EnumerateFiles(@"c:\", "*", SearchOption.AllDirectories))
{
   // whatever
}

在此.NET方法存在之前,通过在字符串数组返回方法上实现递归迭代器,可以实现大致相同的效果。但它并不像新的.NET方法那么懒惰。

那该怎么办?使用此方法时,UnauthorizedAccessException可以被抑制还是生活中的事实?

在我看来,该方法应该有一个重载接受一个处理任何异常的动作。

7 个答案:

答案 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而不是抛出异常;
  • 对于不同版本的.NET Framework,此行为是一致的;
  • 我没有犯任何逻辑或语法错误。

即使它有效,请不要在生产中使用它! 但我真的很想知道是不是。

答案 3 :(得分:0)

基于strudso的答案,但作为FileInfoDirectoryInfo的扩展方法。

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中没有这个问题。通过搜索整个系统驱动器进行了测试。