.net filesystems lazy-evaluation unauthorizedaccessexcepti

.net - Directory.EnumerateFiles=> UnauthorizedAccessException



filesystems lazy-evaluation (5)

Hay un nuevo y agradable método en .NET 4.0 para obtener archivos en un directorio de forma continua mediante la enumeración.

El problema aquí es que si uno desea enumerar todos los archivos, es posible que uno no sepa con antelación qué archivos o carpetas tienen acceso protegido y puede lanzar una excepción de acceso no autorizada.

Para reproducir, uno solo puede ejecutar este fragmento:

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

Antes de que existiera este método .NET, fue posible lograr aproximadamente el mismo efecto implementando un iterador recursivo en los métodos de retorno de la matriz de cadenas. Pero no es tan vago como el nuevo método .NET.

¿Entonces lo que hay que hacer? ¿Puede suprimirse la excepción de acceso no autorizado o es un hecho real al usar este método?

Me parece que el método debe tener una sobrecarga aceptando una acción para tratar con cualquier excepción.


Basado en la respuesta de strudso, pero como métodos de extensión para FileInfo y 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; }


El problema con la respuesta anterior es que no se ocupa de la excepción en los subdirectorios. Esta sería una mejor manera de manejar esas excepciones para que obtenga TODOS los archivos de TODOS los subdirectorios, excepto aquellos con los que lanzó una excepción de acceso:

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


Entiendo que MoveNext arroja la excepción.

Traté de escribir un método que conduzca con seguridad una secuencia e intente ignorar las excepciones de MoveNext . Sin embargo, no estoy seguro de si MoveNext avanza la posición cuando arroja una excepción, por lo que podría ser un ciclo infinito. También es una mala idea porque dependeríamos de los detalles de implementación.

¡Pero es tan divertido!

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()) { // ... }

Esto solo funcionará si las siguientes condiciones son verdaderas sobre la implementación del marco de este iterador (ver FileSystemEnumerableIterator<TSource> en Reflector para referencia):

  • MoveNext avanza su posición cuando falla;
  • Cuando MoveNext falla en el último elemento, las llamadas siguientes devolverán false lugar de arrojar una excepción;
  • Este comportamiento es consistente para diferentes versiones de .NET Framework;
  • No he cometido ningún error de lógica o de sintaxis.

Incluso si funciona, ¡por favor, nunca lo use en producción!
Pero realmente me pregunto si lo hace.


Llego tarde, pero sugiero utilizar un patrón observable:

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


No pude hacer funcionar lo anterior, pero aquí está mi implementación, la he probado en c: / users en un cuadro "Win7", porque si tiene todos estos directorios "desagradables":

SafeWalk.EnumerateFiles(@"C:/users", "*.jpg", SearchOption.AllDirectories).Take(10)

Clase:

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