由于都是手打的字,难免会出现错误,请大家指正,感激。比如下面出现的LINQ、LInq、LINq等都是指的一个东西:LINQ
LINQ查询
- lINQ是Language Integrated Query的简称,他可以视为一组语言和框架的特性的集合,我们可以使用LINQ对本地对象和远程数据源进行结构化的类型安全的检查。LINQ在C# 3.0 /Framework 3.5时引入。
- LINQ用于查询任何实现了
IEnumerable<T>
的集合类型,数组啊,list啊,远程的数据源啊等等,LINQ具有编译时类型检查和动态查询组合这两大优点。 - LINQ中所有核心元素都是在System.Linq和System.Linq.Expressions中。
入门
- LINQ数据源的基本组成部分是序列和元素,序列是指任何实现了
IEnumerable<T>
的对象,元素是指这个序列中的每一个成员。像下面这个names:
string[] names = { "Tom", "Dick", "Harry" };
names是序列也是内存中的一个对象集合,我们也可以称之为本地序列,其中的Tom、Dick等是元素。 - 查询运算符用于转换序列,通常,一个查询运算符接收一个输入的序列,并经过转换为一个输出序列,在System.Linq中定义了约40中运算符,这些运算符都是静态的扩展方法,成为标准查询运算符。
- 提示我们把对本地序列进行的查询叫做本地查询或LInqToObject查询。Linq还支持对那些从远程数据源动态获取的序列进行查询。这些序列需要实现
IQueryble<T>
接口,而在Queryble<T>
类中则有一组相应的标准查询运算符进行支持,一个查询可以理解为使用一个查询运算符对一个序列进行转换的表达式。最简单的查询包含一个查询运算符和一个输入序列。例如我们使用where运算符:
string[] names = { "Tom", "Dick", "Harry" };
IEnumerable<string> filteredNames = System.Linq.Enumerable.Where
(names, n => n.Length >= 4);
foreach (string n in filteredNames)
Console.WriteLine (n);
输出:
Dick
Harry
因为查询运算符都是扩展方法,所以我们可以像调用实例方法那样在names上面调用where:
IEnumerable<string> filteredNames = names.Where (n => n.Length >= 4);
想要这些通过编译,你必须导入System.Linq命名空间,下面这个完整的例子:
using System;
usign System.Collections.Generic;
using System.Linq;
class LinqDemo
{
static void Main()
{
string[] names = { "Tom", "Dick", "Harry" };
IEnumerable<string> filteredNames = names.Where (n => n.Length >= 4);
foreach (string name in filteredNames) Console.WriteLine (name);
}
}
Dick
Harry
提示:实际上我们也可以通过var来定义查询的结果集进一步精简代码,但是这样会降低可读性,并且在vs以外的IDE中也不会得到这种智能提示。
大多数查询运算符都接受一个lambda表达式作为参数,lambda表达式用于对查询进行格式化。本例中的lambda表达式如下:n=>n.Length>=4
这里的输入参数对应一个输入元素,办理中,输入参数n序列中的一个元素,可以看出是string类型的。Where查询运算符要求这个lambda表达式返回一个bool值,如果返回true,那么表示这个元素应该包含在返回的序列里。下面是Where运算符的签名:
public static IEnumerable<TSource> Where<TSource>
(this IEnumerable<TSource> source, Func<TSource,bool> predicate)
下面的查询要找出序列中所有包含字母a的单词的集合:
IEnumerable<string> filteredNames = names.Where (n => n.Contains ("a"));
foreach (string name in filteredNames)
Console.WriteLine (name); // Harry
到目前为止,我们使用了标准查询运算符和lambda表达式进行编写的Linq查询语句,这种查询方式是可以连续拼接起来使用的,我们称这种方法为运算符流语法。C#还提供了另外一种语法来编写linq语句 ,叫做查询表达式语法,使用这种查询表达式,可以将前面的语句编写成:
IEnumerable<string> filteredNames = from n in names
where n.Contains ("a")
select n;
这两种语法是互补的,在下面的文字中,我们会详细的介绍每一种用法。
运算符流语法
运算符流语法是最基本的查询语法,我们后续还会介绍连续使用多个查询运算符进行查询的语句,从中我们可以看到扩展方法的强大功能。此外,还会介绍lambda表达式的书写规则和几个新的查询运算符。
连续使用查询运算符
前面的介绍使用的都是一个查询运算符,这次使用多个:
using System;
using System.Collections.Generic;
using System.Linq;
class LinqDemo
{
static void Main()
{
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);
}
}
JAY
MARY
HARRY
这里先用Where将序列中带a的名字都拿出来,再把新的序列中的元素按照长度排序,最后将这些名字都转化成大写后再输出。
需要注意的是,上面的每个查询运算符中的n都是各自独立的,私有的。可以用下面的示例说明这三个n的关系:
void Test()
{
foreach (char c in "string1") Console.Write (c);
foreach (char c in "string2") Console.Write (c);
foreach (char c in "string3") Console.Write (c);
}
Where、OrderBy、Select都是标准查询运算符,对应System.Linq命名空间中的三个扩展方法。Where查询运算符的作用是筛选传进来的序列,OrderBy是都传进来的序列进行排序,而Select的作用是投影,将参数内的lambda表达式的参数n进行一个提取,甚至可以生成一个和之前序列毫无关系的对象,一句话总结:Where对序列进行的是纵向的缩小(把没用的行都扔了),Select对序列进行的是横向的缩小(把元素的一些没用的字段都扔了)。特别需要注意的是查询运算符只是生成了一个新的序列,并不会改变原有的序列,这种设计师符合函数式编程规范的,LINQ的思想实际上起源于函数式编程。
下面是这三种运算符多对应的扩展方法:
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)
当我们以上面的实例的方式连续使用三个查询运算符进行查询时,可以将各个运算符的处理过程理解成一个传送带,从中可以更直观的看出每一个运算符的输入及输出:
当然我们也可以把他们分别弄成一个语句,而不是把他们放在一个语句中:
// You must import the System.Linq namespace for this to compile:
IEnumerable<string> filtered = names .Where (n => n.Contains ("a"));
IEnumerable<string> sorted = filtered.OrderBy (n => n.Length);
IEnumerable<string> finalQuery = sorted .Select (n => n.ToUpper());
这里的finalQuery得到的结果和上面的结果是一样的。实际上这三个语句每一个都会输出一个新的集合结果,例如:
foreach (string name in filtered)
Console.Write (name + "|"); // Harry|Mary|Jay|
Console.WriteLine();
foreach (string name in sorted)
Console.Write (name + "|"); // Jay|Mary|Harry|
Console.WriteLine();
foreach (string name in finalQuery)
Console.Write (name + "|"); // JAY|MARY|HARRY|
扩展方法的重要性
前面我们提到,每个查询运算符对应着一个扩展方法,前面的示例中我们都是使用查询运算符对集合进行操作,实际上,也可以直接使用这些扩展方法来完成查询,例如:
IEnumerable<string> filtered = Enumerable.Where (names,
n => n.Contains ("a"));
IEnumerable<string> sorted = Enumerable.OrderBy (filtered, n => n.Length);
IEnumerable<string> finalQuery = Enumerable.Select (sorted,
n => n.ToUpper());
实际上这也是编译器在遇到标准查询运算符的处理方式,把它们编译成对应函数进行调用,但是,这种调用方式是不可取的,举个例子来说:如果我们想在一个LINQ表达式中使用多个运算符,可以这样写:
IEnumerable<string> query = names.Where (n => n.Contains ("a"))
.OrderBy (n => n.Length)
.Select (n => n.ToUpper());
这种写法可以很清楚的表示出数据是从左边到右边一次处理的,但是如果直接调用Enumerable类中的方法来完成这个查询,表达式就不那么容易理解了:
IEnumerable<string> query =
Enumerable.Select (
Enumerable.OrderBy (
Enumerable.Where (
names, n => n.Contains ("a")
), n => n.Length
), n => n.ToUpper()
);
使用Lambda表达式
在前面的实例中,我们把如下Lambda表达式作为参数传给了Where运算符:
n => n.Contains ("a") // Input type=string, return type=bool.//返回一个bool值的表达式我们可以称之为断言。
对于不同的查询运算符来说,Lambda表达式的作用也不同。在Where运算符中,Lambda表达式用于判断一个元素是否应该被包含在输出序列中。也就是判断元素是否符合筛选条件;在OrderBy运算符中,Lambda表达式用于将集合中的每个元素映射到他们的排序键上;在Select运算符中,Lambda表达式用于定义输入序列以何种方式、格式进行输出。需要注意的是,查询运算符的Lambda表达式针对的是集合中的每个元素,而不是集合整体。
实际上,运算符会自动识别传递给他的Lambda表达式的意义,典型的情况是:Lambda表达式会作用于序列中的每个元素,并且在操作每个元素的时候都会都对Lambda表达式进行解析,这使得查询运算符看起来很神奇,但它的底层实现是很容易理解的,一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;
}