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

C# 在C-seq vs IEnumerable中编写F递归文件夹访问者,c#,f#,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 IEnumerabl

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

屈服!执行“展平”操作,因此它将您传递的序列集成到外部序列中,隐式地对序列的每个元素执行foreach并对每个元素进行屈服。

没有简单的方法可以做到这一点。 您可以通过定义一个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结果。然而,我相信您可以编写一个优化的版本,它不会有二次复杂度

好的。。我想这是一篇博客文章的主题,而不是可以在这里完整描述的东西:-,但希望它显示出一个想法,您可以尝试以下内容


[编辑:但当然,您也可以使用Flatte的朴素实现,使用SelectMany只是为了使C迭代器代码的语法更准确]

在检索特定目录下的所有文件的特定情况下,效果最好:

static IEnumerable<string> Visitor( string root, string filter ) {
  return Directory.GetFiles( root, filter, SearchOption.AllDirectories );
}
在遍历可枚举对象树的一般情况下,需要嵌套的foreach循环或等效循环。另请参见:

编辑:添加了将任意树展平到枚举中的函数示例:

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

在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中搜索某些目录时,您会遇到这种情况。

那么我可以做一个。选择Many在C中重现这种情况吗?C的收益回报一次只能返回一个项目,因此您必须继续努力,恐怕。事实上,这种超载有一个严重的实际问题;也就是说,如果搜索空间中的任何文件或目录由于路径过长或用户没有适当的权限或任何其他IO异常而无效,则整个操作将中止,并且不返回任何结果。相比之下,使用手动递归搜索,则不存在此类问题;您可以尝试分别捕获每个目录的列表。