做你想做的最实用的方法是:
IEnumerable<FileObject> EnumerateImagesInPath(string path, int firstAssignedID) =>
Enumerable.Zip(
Enumerable.Range(firstAssignedID, Int32.MaxValue),
Directory.EnumerateFiles(path),
FileObject.New);
FileObject 类型定义如下:
public class FileObject
{
public readonly int Id;
public readonly string Filename;
FileObject(int id, string fileName)
{
Id = id;
Filename = fileName;
}
public static FileObject New(int id, string fileName) =>
new FileObject(id, fileName);
}
它不使用yield,但这没关系,因为Enumerable.Range 和Enumerable.Zip 使用,所以它是一个惰性函数,就像你原来的例子一样。
我使用Enumerable.Range 创建一个惰性整数列表,从firstAssignedId 到Int32.MaxValue。这与目录中的可枚举文件一起压缩。 FileObject.New(id. path) 作为 zip 计算的一部分被调用。
没有像接受的答案(firstAssignedID++)那样的就地状态修改,整个函数可以表示为一个表达式。
实现目标的另一种方法是使用fold 模式。它是函数式编程中最常见的聚合状态方式。这是为IEnumerable定义它的方法
public static class EnumerableExt
{
public static S Fold<S, T>(this IEnumerable<T> self, S state, Func<S, T, S> folder) =>
self.Any()
? Fold(self.Skip(1), folder(state, self.First()), folder)
: state;
}
您应该能够看到它是一个递归函数,它在列表的头部运行一个委托 (folder),然后在递归调用 Fold 时将其用作新状态。如果到达列表的末尾,则返回聚合状态。
您可能会注意到 EnumerableExt.Fold 的实现可能会在 C# 中炸毁堆栈(因为缺少尾调用优化)。因此,实现Fold 函数的更好方法是强制执行:
public static S Fold<S, T>(this IEnumerable<T> self, S state, Func<S, T, S> folder)
{
foreach(var x in self)
{
state = folder(state, x);
}
return state;
}
Fold 有一个对偶,称为FoldBack(有时它们被称为“向左折叠”和“向右折叠”)。 FoldBack 本质上是从列表的尾部到头部的聚合,其中Fold 是从头部到尾部的聚合。
public static S FoldBack<S, T>(this IEnumerable<T> self, S state, Func<S, T, S> folder)
{
foreach(var x in self.Reverse()) // Note the Reverse()
{
state = folder(state, x);
}
return state;
}
Fold 非常灵活,例如,您可以为 fold 的可枚举实现 Count,如下所示:
int Count<T>(this IEnumerable<T> self) =>
self.Fold(0, (state, item) => state + 1);
或Sum 像这样:
int Sum<int>(this IEnumerable<int> self) =>
self.Fold(0, (state, item) => state + item);
或者大部分IEnumerable API!
public static bool Any<T>(this IEnumerable<T> self) =>
self.Fold(false, (state, item) => true);
public static bool Exists<T>(this IEnumerable<T> self, Func<T, bool> predicate) =>
self.Fold(false, (state, item) => state || predicate(item));
public static bool ForAll<T>(this IEnumerable<T> self, Func<T, bool> predicate) =>
self.Fold(true, (state, item) => state && predicate(item));
public static IEnumerable<R> Select<T, R>(this IEnumerable<T> self, Func<T, R> map) =>
self.FoldBack(Enumerable.Empty<R>(), (state, item) => map(item).Cons(state));
public static IEnumerable<T> Where<T>(this IEnumerable<T> self, Func<T, bool> predicate) =>
self.FoldBack(Enumerable.Empty<T>(), (state, item) =>
predicate(item)
? item.Cons(state)
: state);
它非常强大,并且允许为集合聚合状态(因此这允许我们在没有必要的就地状态修改的情况下进行firstAssignedId++)。
我们的FileObject 示例比Count 或Sum 稍微复杂一点,因为我们需要维护两个状态:聚合ID 和生成的IEnumerable<FileObject>。所以我们的状态是Tuple<int, IEnumerable<FileObject>>
IEnumerable<FileObject> FoldImagesInPath(string folderPath, int firstAssignedID) =>
Directory.EnumerateFiles(folderPath)
.Fold(
Tuple.Create(firstAssignedID, Enumerable.Empty<FileObject>()),
(state, path) => Tuple.Create(state.Item1 + 1, FileObject.New(state.Item1, path).Cons(state.Item2)))
.Item2;
您可以通过为Tuple<int, IEnumerable<FileObject>> 提供一些扩展和静态方法来使其更具声明性:
public static class FileObjectsState
{
// Creates a tuple with state ID of zero (Item1) and an empty FileObject enumerable (Item2)
public static readonly Tuple<int, IEnumerable<FileObject>> Zero =
Tuple.Create(0, Enumerable.Empty<FileObject>());
// Returns a new tuple with the ID (Item1) set to the supplied argument
public static Tuple<int, IEnumerable<FileObject>> SetId(this Tuple<int, IEnumerable<FileObject>> self, int id) =>
Tuple.Create(id, self.Item2);
// Returns the important part of the result, the enumerable of FileObjects
public static IEnumerable<FileObject> Result(this Tuple<int, IEnumerable<FileObject>> self) =>
self.Item2;
// Adds a new path to the aggregate state and increases the ID by one.
public static Tuple<int, IEnumerable<FileObject>> Add(this Tuple<int, IEnumerable<FileObject>> self, string path) =>
Tuple.Create(self.Item1 + 1, FileObject.New(self.Item1, path).Cons(self.Item2));
}
扩展方法捕获对聚合状态的操作,并使生成的fold 计算非常清晰:
IEnumerable<FileObject> FoldImagesInPath(string folderPath, int firstAssignedID) =>
Directory.EnumerateFiles(folderPath)
.Fold(
FileObjectsState.Zero.SetId(firstAssignedID),
FileObjectsState.Add)
.Result();
显然,在您提供的用例中使用Fold 是多余的,这就是我改用Zip 的原因。但是您遇到的更普遍的问题(功能聚合状态)是 Fold 的用途。
我在上面的示例中使用了另外一种扩展方法:Cons:
public static IEnumerable<T> Cons<T>(this T x, IEnumerable<T> xs)
{
yield return x;
foreach(var a in xs)
{
yield return a;
}
}
更多信息cons can be found here
如果您想了解有关在 C# 中使用函数式技术的更多信息,please check my library: language-ext。它会为您提供大量 C# BCL 所缺少的东西。