Directory.EnumerateFiles => UnauthorizedAccessException - PullRequest
37 голосов
/ 24 февраля 2011

В .NET 4.0 есть замечательный новый метод для потоковой передачи файлов в каталог с помощью перечисления.

Проблема здесь в том, что если кто-то хочет перечислить все файлы, он может не знать заранеекакие файлы или папки защищены от доступа и могут вызвать исключение UnauthorizedAccessException.

Чтобы воспроизвести, можно просто запустить этот фрагмент:

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

До того, как этот метод .NET существовал, можно было достичь примерно того же эффекта, реализовав рекурсивный итератор для массива строк.возвращающие методы.Но он не такой ленивый, как новый метод .NET.

Так что же делать?Может ли UnauthorizedAccessException быть подавленным или является фактом жизни при использовании этого метода?

Мне кажется, что метод должен иметь перегрузку, принимающую действие для обработки любых исключений.

Ответы [ 5 ]

28 голосов
/ 11 мая 2011

Я не могу заставить вышеописанное работать, но вот моя реализация, я проверил ее на c: \ users на коробке "Win7", потому что если есть все эти "грязные" директории:

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>();
        }
    }
}
8 голосов
/ 21 декабря 2013

Проблема с вышеприведенным ответом заключается в том, что он не заботится об исключениях в подкаталогах. Это был бы лучший способ обработки этих исключений, поэтому вы получаете ВСЕ файлы из ВСЕХ подкаталогов, кроме тех, для которых выброшено исключение доступа:

    /// <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;
    }
3 голосов
/ 24 февраля 2011

Я понимаю, что 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())
{
    // ...
}

Это будет работать, только если выполняются следующие условияЗначение true для реализации этого итератора в рамках инфраструктуры (см. FileSystemEnumerableIterator<TSource> в Reflector для справки):

  • MoveNext улучшает свою позицию при сбое;
  • Когда MoveNext завершается неудачей в последний разэлемент, последующие вызовы будут возвращать false вместо выдачи исключения;
  • Это поведение согласованно для разных версий .NET Framework;
  • Я не допустил логических или синтаксических ошибок.

Даже если это работает, пожалуйста, никогда не используйте его в производстве!
Но мне действительно интересно, если это работает.

0 голосов
/ 19 июня 2018

Я опоздал, но вместо этого я предлагаю использовать наблюдаемый шаблон:

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));
  }
}
0 голосов
/ 23 января 2012

На основании ответа Струдсо, но как методы расширения для 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;
}
...