在C#中编写F#递归文件夹访问者 - seq vs IEnumerable

时间:2008-11-21 11:26:38

标签: c# f#

我经常在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'?

4 个答案:

答案 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中搜索某些目录)。