LINQ学习笔记:Lambda表达式
1. 查询操作链
为了构造更加复杂的查询, 我们可以增加而外的查询操作方法, 形成一个查询链. 例如下面的例子:
1: string[] names = { "James","Jack","Harris"};
2:
3: IEnumerable<string> query = names.Where(n => n.Contains ("a")).OrderBy (n => n.Length).Select (n => n.ToUpper( ));
4:
5: foreach (string name in query)
6: Console.Write (name + ","); //JACK,JAMES,HARRIS,
Where, OrderBy和Select都是标准的查询操作并且对应于Enumerable类中的扩展方法. Where会生成一个输入序列的过滤版本, OrderBy则生成一个输入序列的排序版本, Select方法生成一个新的序列, 此序列中的每个元素都是根据给定的lambda表达式进行了转换或者返回. 整个数据流程是从左到右, 因此数据将先被过滤, 然后排序, 最后才被返回.
一个查询操作不会去改变输入序列, 相反的, 它总是返回一个新的序列. 这点上LINQ受到了函数式编程的影响, 并且保持一致的风格.
当查询操作被串联在一起的时候, 每一个操作生成的输出序列将会被作为下一个操作的输入序列. 因此我们也可以使用另外一种渐进式的查询写法:
1: var filtered = names.Where(n => n.Contains ("a"));
2: var sorted = filtered.OrderBy(n => n.Length);
3: var finalQuery = sorted.Select(n => n.ToUpper());
最终我们便利finalQuery得到的结果将会跟上面的例子是一样的:
1: foreach (string name in finalQuery)
2: Console.Write (name + ","); // JACK,JAMES,HARRIS,
2. 编写Lambda表达式
让我们看一下一个最简单的Lambda表达式,n => n.Contains(“a” ) , 在这个表达式中,输入类型是string, 输出类型是bool. 如果一个Lambda表达式返回一个bool值的话我们将它称为一个判定(predicate). Lambda表达式的目的是取决于特定的查询操作符. 例如如果是Where操作符的话, 它指示一个元素是否将会被包含在输出序列中. 而如果是OrderBy操作符的话, Lambda表达式将会把输入序列中的每一个元素映射到它的排序键上. 如果是Select操作符, Lambda表达式将决定输入序列中的每个元素在被放入输出序列之前应该如何转换. 在一个操作中包含的Lambda表达式总是应用于输入序列中的每一个单一元素, 而不是整个序列.
由于Lambda表达式是一个callback, 我们可以在其中放入我们自己的逻辑, 这令查询操作非常的强大, 并且简单. 我们可以看一下Enumerable.Where的实现, 忽略异常处理:
1: public static IEnumerable<TSource> Where<TSource> (
2: this IEnumerable<TSource> source,
3: Func<TSource,bool> predicate)
4: {
5: foreach (TSource element in source)
6: if (predicate (element))
7: yield return element;
8: }
2.1 Lambda表达式和Func标记
在标准的查询操作符中利用了范型Func代理, 它们存在于System.Linq中, 并且定义如下
Func中出现的参数顺序类型跟Lambda表达式中出现的一致. 因此, Func<TSource,bool>匹配一个TSource => bool的Lambda表达式, 它接受一个TSource类型的参数并且返回一个布尔值. 类似的, Func<TSource, TResult>匹配一个TSource => TResult的Lambda表达式. 以下是所有的Func代理定义(注意返回值的类型总是最后一个参数的类型):
delegate TResult Func <T> ( );
delegate TResult Func <T, TResult> (T arg1);
delegate TResult Func <T1, T2, TResult> (T1 arg1, T2 arg2);
delegate TResult Func <T1, T2, T3, TResult> (T1 arg1, T2 arg2, T3 arg3);
delegate TResult Func <T1, T2, T3, T4, TResult> (T1 arg1, T2 arg2, T3 arg3, T4 arg4);
2.2 Lambda表达式和元素命名
标准的查询操作使用以下的类型命名:
TSource: 输入序列中的元素类型
TResult: 输出序列中的元素类型 – 如果它是不同于TSource的话
TKey: 用于sorting, grouping, join中的key类型
TSource是由输入序列决定的, 而TResult和Tkey则由Lambda表示式来推断. 例如, 考虑以下的Select操作:
1: static IEnumerable<TResult> Select<TSource,TResult> (
2: this IEnumerable<TSource> source,
3: Func<TSource,TResult> selector)
Func<TSource,TResult>匹配一个TSource=>TResult的Lambda表达式, 一个对应于输入元素, 另一个则对应于输出元素. TSource和TResult是不同的类型, 因此Lambda表达式可以更改每一个元素的类型. 更进一步, Lambda表达式决定了输出序列的类型. 例如:
1: string[] names = { "James","Jack","Harris"};
2: IEnumerable<int> query = names.Select (n => n.Length);
3: foreach (int length in query)
4: Console.Write (length); // 546
3. 自然排序
针对一个输入序列的排序功能在LINQ中是非常重要的. 一些操作符, 例如Take, Skip和Reverse都依赖于这个行为. Take操作符输出头x个元素, 而排除剩余的, Skip操作符则忽略了头x个元素, 输出所有余下的. Reverse操作符将一个序列中的元素进行反转.
4. 其他操作符
并非所有的操作都回返回一个序列, 针对于元素的操作符都只返回一个元素, 例如First, Last, Single, ElementAt等等:
1: int[] myNumbers = { 7,6,5,4,3};
2: int fn = myNumbers.First(); // 7
3: Console.WriteLine(fn);
4: int ln = myNumbers.Last(); // 3
5: Console.WriteLine(ln);
6: int sn = myNumbers.ElementAt(1); // 6
7: Console.WriteLine(sn);
另外聚合操作符则返回一个单一值:
1: int count = myNumbers.Count(); // 5;
2: Console.WriteLine(count);
3: int min = myNumbers.Min(); // 3;
4: Console.WriteLine(min);
另外Contains,Any则返回bool值. 这些操作符并不返回一个集合, 因此他们不能在他们的结果集中再次调用其它的操作, 换句话说他们必须出现在整个查询链中的最后一个(或者是子查询.) 另外有些操作符接受两个输入序列, 例如Contact, 它将会将一个序列添加到另外一个当中去, 而Union类似, 区别是它会自动删除其中重复的元素. 待续!