Подтвердить что ты не робот

Directory.EnumerateFiles => UnauthorizedAccessException

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

    /// <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

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

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

Ответ 3

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

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

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

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

Ответ 4

На основе ответа 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;
}