首页 > 代码库 > 10.2.3.2 在 C# 中以函数风格使用数组
10.2.3.2 在 C# 中以函数风格使用数组
10.2.3.2 在 C# 中以函数风格使用数组
由于有了 LINQ to Object,在 C# 3.0 中,我们已经可以使用许多函数结构来处理数组。大多数 LINQ 运算符不返回数组:如果在数组上调用 Enumerable.Select,结果将返回 IEnumerable<T>。在某些情况下,我们还是愿意将结果保存在数组中,避免调用Enumerable.ToArray,将结果序列复制回数组的开销。
针对数组的一些常用函数式操作,已经成为 System.Array 类中的静态方法;但它们的命名规则不同于 F# 和 LINQ,例如,你会发现映射操作,在 ConvertAll 名下。我们将用标准的名字,实现我们的操作版本,进行演示。清单10.14 中还增加了类似于 F# 的 Array.int 函数的方法。
清单 10.14 函数式处理数组的方法(C#)
static class ArrayUtils {
publicstatic T[] int<T>(int length, Func<int, T> init) { [1]
T[] arr = new T[length];
for (int i = 0; i < length; i++) arr[i] = init(i);
return arr;
}
publicstatic R[] Select<T, R>(this T[] arr, Func<T, R> map) { [2]
R[] res = new R[arr.Length];
for (int i = 0; i < arr.Length; i++) res[i] = map(arr[i]);
return res;
}
}
int 方法是通常的静态方法[1],它的参数为函数 init,用来初始化数组中的元素。Select 方法是扩展方法,把映射函数应用到原来数组中的每个元素,结果返回一个新数组的;它隐藏了由 LINQ 提供的、标准的 Select 操作。我们来使用这些方法的方式,与以前对应的 F# 函数类似:
var rnd = new Random();
var numbers = ArrayUtils.int(5, n =>rnd.Next(20));
var squares = numbers.Select(n => new {Number = n, Square = n*n });
foreach (var sq in squares)
Console.Write("({0}, {1}) ", sq.Number, sq.Square);
就像在 F# 版本一样,数组一旦创建,就不能修改。从更高层次来看,它就是处理不可变数据结构的纯函数代码;当然,我们实际能够进行修改,但仅限于 ArrayUtils 类中,c及只有在没有公开给任何其他代码的集合上进行。修改对外面是不可见的;在 C# 中,以这种方式写代码,是更有价值的,因为,这里使用函数式列表要比在 F# 中更难。
我们本章的最后一个主题是关于连续(continuations)的。可能有点难于理解,但是,一旦理解以后,可能会很吃惊。好消息是,如果你曾经在 .NET 中过写任何异步代码,从某种意义上说,就已经使用了连续,而 F# 使之更容易。我们将在第十三章更详细讨论,使用连续,对于递归函数是非常重要的优化方法,因此,我们将在这里关注一下。
10.2.3.2 在 C# 中以函数风格使用数组