12.5.4 在 C# 中实现查询操作
在清单 12.14 中,当我们讨论序列和分析 SelectMany 操作时,已经知道可以把 C# 查询转换为方法调用。我们实现的查询,只支持以 select 子句结尾,而忽视那些仅对集合有用的情况,比如分组,这样,就需要实现 Select 扩展方法。
我们前面说过,第二个和随后的 from 子句会转换成对 SelectMany 方法的调用。当使用查询写计算时,我们使用 from 子句的方式,非常类似于F# let! 结构,表示非标准的值绑定,所以,经常会用到它,这就是说,我们还需要为 ValueWrapper <‘T> 类型实现 SelectMany 操作。
我们已经知道,SelectMany 方法对当于 bind 函数,且要稍微复杂一些,因为它有额外的函数,需要在返回结果之前运行。Select 方法更简单,但是,我们会在看过这段代码之后,再讨论,清单 12.20 实现了两个基本操作。
清单12.20 实现查询操作 (C#)
static class ValueWrapperExtensions {
public static ValueWrapper<R>Select<T, R>
(thisValueWrapper<T> source,
Func<T, R> selector) {
return newValueWrapper<R>(selector(source.Value)); <-- 用给定函数映射值
}
public static ValueWrapper<R>SelectMany<T, V, R>
(thisValueWrapper<T> source,
Func<T, ValueWrapper<V>> valueSelector,
Func<T, V, R> resultSelector) {
var newVal =valueSelector(source.Value); [1]
var resVal = resultSelector(source.Value,newVal.Value); [2]
return newValueWrapper<R>(resVal); [3]
}
}
两个方法都实现为扩展方法。这就是说,当处理类型 ValueWrapper <T> 的值时,使用标准的点表示法,用于从查询语法转换期间,C# 将能够找到它们。Select 操作使用给定的函数实现映射,因此,只需要访问包装的值,运行给定的函数,然后,包装结果。
SelectMany 操作开始有点乱,但是,看一下参数的类型,还是有用的,它可以告诉我们,可以传递什么参数给什么函数。开始像 F# 的 Bind 成员一样,在提取了第一个参数值之后,通过调用由第二个参数值给定的函数[1];我们还必须把源值,与第一个函数的返回值组合起来,为了得到结果,我们以这两个值作为参数,调用第二个函数[2];最后,把结果打包成计算类型[3]],并从方法返回。
在清单 12.18 中,操作实现之后,查询表达式就能编译并运行了。在这一节,我们创建的计算类型,没有为计算增加任何额外的功能。如此简单,非常适合做成标准操作的模板。从模板开始,看看需要修改的地方,可以实现更复杂的单子类型。现在,我们把理论联系实际,为选项类型实现类似的操作。