LINQ之路 4:LINQ方法语法
书写LINQ查询时又两种语法可供选择:方法语法(Fluent Syntax)和查询语法(Query Expression)。
LINQ方法语法是非常灵活和重要的,我们在这里将描述使用链接查询运算符的方式来创建复杂的查询,方法语法的本质是通过扩展方法和Lambda表达式来创建查询。C# 3.0对于LINQ表达式还引入了声明式的查询语法,通过查询语法写出的查询比较类似于SQL查询。本篇会对LINQ方法语法进行详细的介绍。
当然,.NET公共语言运行库(CLR)并不具有查询语法的概念。所以,编译器会在程序编译时把查询表达式转换为方法语法,即对扩展方法的调用。所以使用方法语法会让我们更加接近和了解LINQ的实现和本质,并且一些查询只能表示为方法调用,如检索序列中的最大值、最小值元素的查询,他们在查询语法中就没有对应的实现。但另一方面,查询语法通常会比较简单和易读。不管怎样,这两种语法和互相补充和兼容的,我们可以在一个查询中混合使用方法语法和查询语法。
链接查询运算符
在LINQ介绍中,我们示范了使用单个查询运算符创建的查询。如果需要创建更加复杂的查询,我们可以在表达式之后添加其他查询运算符,产生一个查询链。如下例:查询出所有含有字母”a”的姓名,按长度进行排序,然后把结果全部转换成大写格式。
static void Main(string[] args)
{
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; IEnumerable<string> query = names
.Where(n => n.Contains("a"))
.OrderBy(n => n.Length)
.Select(n => n.ToUpper()); foreach (string name in query) Console.WriteLine(name);
} // Result:
JAY
MARY
HARRY
就像在本例中所示,当链接使用查询运算符时,一个运算符的输出sequence会成为下一个运算符的输入sequence,其结果形成了一个sequence的传输链,如图所示:

图:链接查询运算符
Where, OrderyBy, Select这些标准查询运算符对应Enumerable类中的相应扩展方法。Where产生一个经过过滤的sequence;OrderBy生成输入sequence的排序版本;Select得到的序列中的每个元素都经过了给定lambda表达式的转换。
下面是Where, OrderBy, Select这几个扩展方法的签名:
public static IEnumerable<TSource> Where<TSource>
(this IEnumerable<TSource> source, Func<TSource, bool> predicate) public static IEnumerable<TSource> OrderBy<TSource, TKey>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) public static IEnumerable<TResult> Select<TSource, TResult>
(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
扩展方法的重要性
在LINQ表达式中,正是扩展方法让LINQ查询运算符的链接成为了可能。因为运算符本身是对IEnumerable<T>类型的扩展,并且返回IEnumerable<T>类型的结果。我们可以比较一下使用扩展方法和使用静态方法的区别,结果会一目了然。扩展方法非常自然地反映了从左到右的数据流向,同时保持lambda表达式与查询运算符的位置一致性。
// extension methods make LINQ elegant
IEnumerable<string> query = names
.Where(n => n.Contains("a"))
.OrderBy(n => n.Length)
.Select(n => n.ToUpper()); // static methods lose query's fluency
IEnumerable<string> query2 =
Enumerable.Select(
Enumerable.OrderBy(
Enumerable.Where(names, n => n.Contains("a")
), n => n.Length
), n => n.ToUpper()
);
创建Lambda表达式
在上例中,我们向Where运算符提供了如下Lambda表达式:n => n.Contains(“a”)。对各个查询运算符来说,Lambda表达式的目的不尽相同。对于Where,它决定了一个element是否包含在结果sequence中;对于OrderBy,它把每个element映射到比较的键值;而对于Select,lambda表达式则决定了输入sequence中的元素要怎么样的转换再放入输出sequence中。
关于Lambda表达式的详细介绍,请参考LINQ 之路3:C# 3.0的语言功能(下)。
通常来说,查询运算符会对每一个输入sequence中的element来调用我们提供的Lambda表达式,这样就给了我们一个实现自己的逻辑,从而得到自己需要结果的机会。我们可以看一下.NET Framework对Enumerable.Where的实现:
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
foreach (TSource element in source)
if (predicate(element))
yield return element;
}
标准查询运算符使用了通用的Func委托,Func是一组定义在System.Linq(谢谢 A_明~坚持的指正,此处应为System)命名空间中的通用委托。它接受一系列的输入参数和一个返回值,返回值对应最后一个参数定义。所以,Func<TSource, bool>委托匹配 TSource => bool表达式,接受TSource输入参数,返回一个bool值。
Lambda表达式和元素类型
标准查询运算符使用统一的类型名称:
|
通用类型名称 |
用途 |
|
TSource |
Input sequence中的element类型 |
|
TResult |
Output sequence中的element 类型(如果和TSource不相同) |
|
TKey |
使用sorting、grouping、joining等的键值类型 |
TSource由输入sequence决定,而TResult和TKey则从我们提供的Lambda表达式推断得到。
比如:Select查询运算符的签名如下:
public static IEnumerable<TResult> Select<TSource,TResult>(
this IEnumerable<TSource> source, Func<TSource,TResult> selector)
Func<TSource,TResult>匹配TSource => TResult的Lambda表达式,接受一个输入参数TSource,返回TResult。因为TSource和TResult是不同的类型,所以我们的Lambda表达式甚至可以改变输入element的数据类型。下面的示例就把string类型元素转换为int类型元素:
static void TestSelectOperator(){
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
// 编译器将会从Lambda表达式 n => n.Length推断出TResult为int类型
IEnumerable<int> query = names.Select(n => n.Length);
foreach (int length in query)
Console.Write(length + "|"); // 3|4|5|4|3
}
而对Where查询运算符来讲,它并不需要对输出element进行类型推断,因为它只是对输入elements进行过滤而不作转换,因此输出element和输入element具有相同的数据类型。
对于OrderBy查询运算符来讲,Func<TSource, TKey>把输入元素映射至一个排序键值。TKey由Lambda表达式的结果推断出来,比如我们可以按长度或按字母顺序对names数组进行排序:
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
IEnumerable<string> sortedByLength, sortedAlphabetically;
sortedByLength = names.OrderBy(n => n.Length); // int key
sortedAlphabetically = names.OrderBy(n => n); // string key
其他查询运算符
并不是所有的查询运算符都返回一个sequence。元素(element)运算符会从输入sequence中获取单个元素,如:First,Last和ElementAt:
int[] numbers = { , , , , };
int firstNumber = numbers.First(); //
int lastNumber = numbers.Last(); //
int secondNumber = numbers.ElementAt(); //
int lowestNumber = numbers.OrderBy(n => n).First(); //
集合(aggregation) 运算符返回一个标量值,通常是数值类型:
int count = numbers.Count(); //
int min = numbers.Min(); //
判断运算符返回一个bool值:
bool hasTheNumberNine = numbers.Contains(); // true
bool hasElements = numbers.Any(); // true
bool hasAnOddElement = numbers.Any(n => (n % ) == ); //true
因为这些运算符并不是返回一个sequence,所以我们不能再这些运算符之后链接其他运算符,换句话讲,他们一般出现在查询的最后面。
还有一些查询运算符接受两个输入sequence,比如Concat把一个sequence添加到另外一个sequence后面;Union与Concat类似,但是会去除相同的元素:
int[] seq1 = { , , , };
int[] seq2 = { , , };
IEnumerable<int> concat = seq1.Concat(seq2); // { 1, 2, 2, 3, 3, 4, 5 }
IEnumerable<int> union = seq1.Union(seq2); // { 1, 2, 3, 4, 5 }
本篇只是对几个常用的查询运算符做了介绍,在后续篇章中,我会对更多地运算符进行更详细的讨论。
LINQ之路 4:LINQ方法语法的更多相关文章
- Linq学习(主要参考linq之路)----2LINQ方法语法
方法语法:Fluent Syntax 方法语法是非常灵活和重要的.我们这里讲描述使用连接查询运算符的方式来创建复杂的子查询,方法语法的本质是通过扩展方法和Lambda表达式来创建查询. eg1: st ...
- LINQ之路16:LINQ Operators之集合运算符、Zip操作符、转换方法、生成器方法
本篇将是关于LINQ Operators的最后一篇,包括:集合运算符(Set Operators).Zip操作符.转换方法(Conversion Methods).生成器方法(Generation M ...
- LINQ之路15:LINQ Operators之元素运算符、集合方法、量词方法
本篇继续LINQ Operators的介绍,包括元素运算符/Element Operators.集合方法/Aggregation.量词/Quantifiers Methods.元素运算符从一个sequ ...
- .NET LINQ查询语法与方法语法
LINQ 查询语法与方法语法 通过使用 C# 3.0 中引入的声明性查询语法,介绍性 LINQ 文档中的多数查询都被编写为查询表达式. 但是,.NET 公共语言运行时 (CLR) 本身并不具 ...
- C#3.0新增功能09 LINQ 基础07 LINQ 中的查询语法和方法语法
连载目录 [已更新最新开发文章,点击查看详细] 介绍性的语言集成查询 (LINQ) 文档中的大多数查询是使用 LINQ 声明性查询语法编写的.但是在编译代码时,查询语法必须转换为针对 .NET ...
- 四:MVC之LINQ方法语法
linq 查询 有两种语法 ,前面我们说了一种,接下来说方法语法(我读着一直很绕口) 查询语法,方法语法 ------------------------以下文字都是复制-------------- ...
- LINQ之方法语法
上节讲到使用linq的查询关键字进行查询,这节讲一下linq查询的另一种方式--linq方法. 使用linq方法语法,必须要会用lambda表达式,配合lambda表达式才能体会到linq的优雅便捷. ...
- LINQ之路 7:子查询、创建策略和数据转换
在前面的系列中,我们已经讨论了LINQ简单查询的大部分特性,了解了LINQ的支持计术和语法形式.至此,我们应该可以创建出大部分相对简单的LINQ查询.在本篇中,除了对前面的知识做个简单的总结,还会介绍 ...
- LINQ之路(2):LINQ to SQL本质
LINQ之路(2):LINQ to SQL本质 在前面一篇文章中回顾了LINQ基本语法规则,在本文将介绍LINQ to SQL的本质.LINQ to SQL是microsoft针对SQL Server ...
随机推荐
- 颜色表及html代码
颜色名称及色样表(HTML版) 颜色名 中文名称 Hex RGB 十进制 Decimal LightPink 浅粉红 #FFB6C1 255,182,193 Pink 粉红 #FF ...
- java对象的序列化与反序列化使用
1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列的过程:而Java反序列化是指把字节序列恢复为Java对象的过程. 2.为什么需要序列化与反序列化 我们知道,当两个进程进 ...
- JS和CSS的多浏览器兼容(3)
3.Javascript的浏览器兼容性问题 3.1 集合类对象问题说明:IE下,可以使用()或[]获取集合类对象; Safari及Chrome下,只能使用[]获取集合类对象. 解决方法:统一使用[]获 ...
- PHP中Redis替代文件存储Session语句
php默认使用文件存储session,如果并发量大,效率非常低.而Redis对高并发的支持非常好,所以,可以使用redis替代文件存储session. 这里,介绍下php的 session_set_s ...
- 《深入浅出MFC:》
callback函数必须是static的,才能去除隐藏的this指针.
- rtc关机闹钟7 jni层 com_android_server_AlarmManagerService
frameworks/base/services/core/jni/com_android_server_AlarmManagerService.cpp int AlarmImplAlarmDrive ...
- Behavior Designer中的内置消息机制
最近在用Behavior Designer,其中需要用到消息机制,看了一下其中自带了这套东西 注册 Owner.RegisterEvent<string>("Message&qu ...
- python中string.casefold和string.lower区别
string.casefold和string.lower 区别 python 3.3 引入了string.casefold 方法,其效果和 string.lower 非常类似,都可以把字符串变成小写, ...
- JAVA基础知识之多线程——三种实现多线程的方法及区别
所有JAVA线程都必须是Thread或其子类的实例. 继承Thread类创建线程 步骤如下, 定义Thead子类并实现run()方法,run()是线程执行体 创建此子类实例对象,即创建了线程对象 调用 ...
- Web项目后台测试流程
1. 本地下载项目源码 1. Git clone项目代码到本地(本地项目代码1)并fetch: 2. Switch到master分支: 3. Create测试分支(例如:test1)并勾选“Switc ...