Scrivere il visitatore della cartella ricorsiva F # in C # – seq vs IEnumerable

Uso spesso questo ‘visitatore’ ricorsivo in F #

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

Recentemente ho iniziato a lavorare sull’implementazione di alcune funzionalità di F # in C # e sto cercando di riprodurlo come IEnumerable, ma ho difficoltà a ottenere oltre questo:

 static IEnumerable 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; } 

Quello che non capisco è perché devo fare un doppio foreach nella versione C # per la ricorsione, ma non in F # … Seq {} implicitamente fa un ‘concat’?

yield! esegue un’operazione “flatten”, quindi integra la sequenza che hai passato nella sequenza esterna, eseguendo implicitamente una foreach su ciascun elemento della sequenza e yield su ciascuno di essi.

Non esiste un modo semplice per farlo. Puoi risolvere questo problema definendo un tipo C # in grado di memorizzare un valore o una sequenza di valori – utilizzando la notazione F # sarebbe:

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

(traduci questo in C # nel modo che preferisci :-))

Ora potresti scrivere qualcosa come:

 static IEnumerable> 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)) } } 

Per usarlo, dovresti scrivere una funzione che appiattisca EnumerationResult, che potrebbe essere un metodo di estensione in C # con la seguente firma:

 IEnumerable Flatten(this IEnumerable> res); 

Ora, questa è una parte in cui diventa complicata – se l’hai implementata in un modo semplice, conterrebbe comunque “forach” per iterare sui risultati “Seq” annidati. Tuttavia, credo che potresti scrivere una versione ottimizzata che non avrebbe una complessità quadratica.

Ok .. Immagino che questo sia un argomento per un post sul blog piuttosto che qualcosa che potrebbe essere completamente descritto qui :-), ma si spera che mostri un’idea che puoi provare a seguire!

[EDIT: Ma ovviamente, puoi anche usare l’ingenua implementazione di “Flatten” che userebbe “SelectMany” solo per rendere più bella la syntax del tuo codice di iterazione C #]

Nel caso specifico di recuperare tutti i file in una directory specifica, questo overload di Directory.GetFiles funziona meglio:

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

Nel caso generale di attraversamento di un albero di oggetti enumerabili, è richiesto un ciclo foreach nidificato o equivalente (vedere anche: Tutto su Iterator ).


Modifica: Aggiunto un esempio di una funzione per appiattire qualsiasi albero in un’enumerazione:

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

Questo può essere usato per selezionare tutti i file annidati, come prima:

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

In C #, utilizzo il seguente codice per questo tipo di funzione:

 public static IEnumerable TryGetDirectories(this DirectoryInfo dir) { return F.Swallow(() => dir.GetDirectories(), () => new DirectoryInfo[] { }); } public static IEnumerable 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); } 

Questo risolve gli errori di I / O (che inevitabilmente accadono, sfortunatamente) ed evita loop infiniti dovuti a collegamenti simbolici (in particolare, ti imbatterai nella ricerca di alcune dir in windows 7).