我经常在F#中使用这个递归的“访客”
let rec visitor dir filter=
seq { yield! Directory.GetFiles(dir, filter)
for subdir in Directory.GetDirectories(dir) do yield! visitor subdir filter}
最近我开始在C#中实现一些F#功能,并且我试图将其重现为IEnumerable,但是我很难得到更多:
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;
}
我不明白为什么我必须在C#版本中为递归做一个双重foreach,而不是在F#中... seq {}是否隐含地执行'concat'?
答案 0 :(得分:13)
yield!
执行'flatten'操作,因此它将您传递给外部序列的序列集成在一起,在序列的每个元素上隐式执行foreach
,并在每个元素上执行yield
之一。
答案 1 :(得分:3)
没有简单的方法可以做到这一点。 您可以通过定义可以存储一个值或一系列值的C#类型来解决此问题 - 使用F#表示法:
type EnumerationResult<'a> =
| One of 'a
| Seq of seq<'a>
(以您喜欢的任何方式将其转换为C#: - ))
现在,您可以写下类似的内容:
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))
}
}
要使用它,你必须编写一个扁平化EnumerationResult的函数,它可以是C#中的扩展方法,具有以下签名:
IEnumerable<T> Flatten(this IEnumerable<EnumerationResult<T>> res);
现在,这是一个棘手的部分 - 如果你以一种直接的方式实现它,它仍然会包含“forach”迭代嵌套的“Seq”结果。但是,我相信您可以编写一个不具有二次复杂度的优化版本。
好的..我想这是一个博客文章的主题,而不是可以在这里完整描述的内容:-),但希望它显示出一个可以尝试跟随的想法!
[编辑:当然,您也可以使用“Flatten”的天真实现,使用“SelectMany”只是为了使您的C#迭代器代码的语法更好]
答案 2 :(得分:2)
在检索特定目录下的所有文件的特定情况下,this overload of Directory.GetFiles
效果最佳:
static IEnumerable<string> Visitor( string root, string filter ) {
return Directory.GetFiles( root, filter, SearchOption.AllDirectories );
}
在遍历可枚举对象树的一般情况下,需要嵌套的foreach循环或等效项(另请参阅:All About Iterators)。
编辑:添加了一个函数示例,将任何树展平为枚举:
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;
}
这可用于选择所有嵌套文件,如前所述:
static IEnumerable<string> Visitor( string root, string filter ) {
return Flatten( root, dir => Directory.GetDirectories( dir ) )
.SelectMany( dir => Directory.GetFiles( dir, filter ) );
}
答案 3 :(得分:2)
在C#中,我使用以下代码来实现这种功能:
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);
}
这解决了IO错误(不幸的是,它不可避免地发生),并且避免了由于符号链接导致的无限循环(特别是,你将遇到在Windows 7中搜索某些目录)。