2008-11-21 23 views
7

A menudo utilizo este recursiva 'visitante' en Fa #Escribir el F # visitante recurrente de la carpeta en C# - SEC vs IEnumerable

let rec visitor dir filter= 
    seq { yield! Directory.GetFiles(dir, filter) 
      for subdir in Directory.GetDirectories(dir) do yield! visitor subdir filter} 

Recientemente he comenzado a trabajar en la implementación de algunas # funcionalidad F en C#, y estoy tratando de reproducir esto como IEnumerable, pero estoy teniendo dificultades para conseguir más allá de esto:

static IEnumerable<string> Visitor(string root, string filter) 
{ 
    foreach (var file in Directory.GetFiles(root, filter)) 
     yield return file; 
    foreach (var subdir in Directory.GetDirectories(root)) 
     foreach (var file in Visitor(subdir, filter)) 
      yield return file; 
} 

lo que no entiendo es por eso que tengo que hacer un doble foreach en la versión C# para la recursividad, pero no en F # ... ¿El seq {} hace implícitamente un 'concat'?

Respuesta

12

yield! hace una operación de 'aplanar', por lo que integra la secuencia que ha pasado en la secuencia externa, de manera implícita la realización de una foreach sobre cada elemento de la secuencia y yield en cada uno.

+1

Entonces, ¿podría hacer un .SelectMany para reproducir esto en C#? – Benjol

+1

El "retorno de rendimiento" de C# solo puede devolver un artículo a la vez, así que estás atascado con tus engaños, me temo. – Sunlight

3

No hay una manera simple de hacer esto. Se podría solucionar este definiendo un C# tipo que puede almacenar ya sea de un valor o de una secuencia de valores - utilizando el # notación F sería:

type EnumerationResult<'a> = 
    | One of 'a 
    | Seq of seq<'a> 

(traducir esto a C#, en cualquier forma que le guste :-))

Ahora, se podría escribir algo como:

static IEnumerable<EnumerationResult<string>> Visitor 
     (string root, string filter) { 
    foreach (var file in Directory.GetFiles(root, filter)) 
     yield return EnumerationResult.One(file); 
     foreach (var subdir in Directory.GetDirectories(root)) 
      yield return EnumerationResult.Seq(Visitor(subdir, filter)) 
    } 
} 

para usarlo, usted tendría que escribir una función que aplana EnumerationResult, lo que podría ser un método de extensión en C# con la siguiente firma:

IEnumerable<T> Flatten(this IEnumerable<EnumerationResult<T>> res); 

Ahora, esta es una parte donde se vuelve complicado: si implementa esto de una manera directa, todavía contendría "forach" para iterar sobre los resultados "Seq" anidados. Sin embargo, creo que podría escribir una versión optimizada que no tenga complejidad cuadrática.

Ok ... Supongo que este es un tema para una publicación de blog en lugar de algo que se puede describir completamente aquí :-), pero con suerte, ¡muestra una idea que puedes intentar seguir!

[EDIT: Pero, por supuesto, también puede utilizar la aplicación ingenua de "aplanar" que utilizaría "SelectMany" sólo para hacer la sintaxis de su código C# iterador más agradable]

2

En el caso específico de recuperar todos los archivos bajo un directorio específico this overload of Directory.GetFiles, funciona mejor:

static IEnumerable<string> Visitor(string root, string filter) { 
    return Directory.GetFiles(root, filter, SearchOption.AllDirectories); 
} 


En el caso general de recorrer un árbol de objetos enumerables, se requiere un bucle foreach anidados o equivalente (véase también: All About Iterators).


Editar: Agregado un ejemplo de una función para aplanar cualquier árbol en una enumeración:

static IEnumerable<T> Flatten<T>(T item, Func<T, IEnumerable<T>> next) { 
    yield return item; 
    foreach(T child in next(item)) 
    foreach(T flattenedChild in Flatten(child, next)) 
     yield return flattenedChild; 
} 

Esto puede ser usado para seleccionar todos los archivos anidados, como antes:

static IEnumerable<string> Visitor(string root, string filter) { 
    return Flatten(root, dir => Directory.GetDirectories(dir)) 
    .SelectMany(dir => Directory.GetFiles(dir, filter)); 
} 
+1

En realidad, esa sobrecarga en particular tiene un problema práctico serio; es decir, si * cualquier * archivo o directorio dentro del espacio de búsqueda no es válido por tener una ruta demasiado larga o porque el usuario no tiene los permisos apropiados o cualquier otra excepción IO, la operación completa se cancela y no arroja resultados. Por el contrario, al utilizar una búsqueda manual recursiva, no hay tales problemas; puede intentar capturar la lista de cada directorio individualmente. –

2

En C#, utilizo el siguiente código para este tipo de función:

public static IEnumerable<DirectoryInfo> TryGetDirectories(this DirectoryInfo dir) { 
    return F.Swallow(() => dir.GetDirectories(),() => new DirectoryInfo[] { }); 
} 
public static IEnumerable<DirectoryInfo> DescendantDirs(this DirectoryInfo dir) { 
    return Enumerable.Repeat(dir, 1).Concat(
     from kid in dir.TryGetDirectories() 
     where (kid.Attributes & FileAttributes.ReparsePoint) == 0 
     from desc in kid.DescendantDirs() 
     select desc); 
} 

Esto soluciona los errores de IO (que inevitablemente suceden, desafortunadamente), y evita bucles infinitos debido a enlaces simbólicos (en particular, se encontrará con esa búsqueda de algunos directorios en Windows 7).

+0

Ambos completos y concisos – Hans

Cuestiones relacionadas