LINQ入门
语言集成查询(Language Integrated Query,LINQ),发音"link",是 .NET Framework 3.5的新特性,其全称是 Language Integrated Query,即语言集成查询,是指将查询功能和语言结合起来。从而为我们提供一种统一的方式,让我们能在C#或VB.NET语言中直接查询和操作各种数据。
LINQ 提供了一条更常规的途径即给 .Net Framework添加一些可以应用于所有信息源( all sources of information )的具有多种用途( general-purpose )的语法查询特性( query facilities ),这是比向开发语言和运行时( runtime )添加一些关系数据( relational )特性或者类似 XML 特性( XML-specific )更好的方式。这些语法特性就叫做 .NET Language Integrated Query (LINQ) 。
LINQ除了提供一个统一的API来操作各种数据,并且为我们提供了编译时类型检查和动态创建查询表达式的能力。
匿名类型
匿名类型(anonymous type)经常用于LINQ查询的结果之中。
如下代码给出了一个创建和使用匿名类型的示例。可以通过赋值形式,简单标识符和成员访问表达式来对匿名类型进行对象初始化。另外,在WriteLine语句中,可以像访问具名类型的成员那样访问实例的成员。
class Other
{
static public string Name = "Mary Jones";
}
class Program
{
static void Main()
{
string Major = "History";
var student = new { Age = 19, Other.Name, Major };//赋值形式,简单标识符和成员访问表达式
Console.WriteLine("{0}, Age {1}, Major {2}", student.Name, student.Age, student.Major);
}
}
方法语法和查询语法
书写LINQ查询时有两种语法可供选择:方法语法(Fluent Syntax)和查询表达式(Query Expression)。
- 方法语法(method syntax)使用标准的方法调用。着这些方法是一组叫做标准查询运算符的方法,是命令式(imperative)的。
- 查询语法(query syntax)看上去和SQL语句很相似,使用查询表达式形式书写,是声明式(declarative)的。
int[] numbers = { 2, 5, 28, 31, 17, 16, 42 };
var numsQuery = from n in numbers //查询语法
where n < 20
select n;
var numsMethod = numbers.Where(x => x < 20); //方法语法
int numsCount = (from n in numbers
where n < 20
select n).Count(); //两种形式的组合
LINQ查询可以返回两种类型的姐结果--枚举或者是标量。其重要区别在于查询执行的时间。枚举处理时才执行(延迟执行),数据改动则会改变,标量则会立即执行查询表达式。
延迟执行带来的一个影响是,当我们重复遍历查询结果时,查询会被重复执行。这个时候,我们就可以利用转换运算符,如ToArray、ToList来避开重复执行,ToArray把查询结果保存至一个Array,而ToList把结果保存至泛型List<>
查询表达式
查询语法通常来讲,是创建LINQ查询的更加快捷的方式。尽管通过查询语法写出的查询比较类似于SQL查询,但实际上查询表达式的产生并不是建立在SQL之上,而是建立在函数式编程语言如LISP和Haskell中的list comprehensions(列表解析)功能之上。
查询表达式由查询体后的from子句组成。其中from子句和select ... group 子句是必需的。
var groupA = new[] { 3, 4, 5, 6 };
var groupB = new[] { 6, 7, 8, 9 };
var someInts = from a in groupA
from b in groupB
let sum = a + b
where sum == 12
where a >= 4
orderby b
select new { a, b, sum };
var students = new[]
{
new { LName="Jones", FName="Mary", Age=19, Mahor="History"},
new { LName="Smith", FName="Bob", Age=20, Mahor="CompSci"},
new { LName="Fleming", FName="Carol", Age=21, Mahor="History"},
};
var query = from stu in students
group stu by stu.Mahor;
foreach (var s in query)
{
Console.WriteLine(s.Key);
}
标准查询运算符
标准查询运算符由一系列API方法组成,它能让我们查询任何.NET数组或集合,并且使用方法语法。共有47个标准查询运算符,可以用来操作一个或多个序列。序列是指实现了IEumerable<>接口的类,包括List<>,Dictionary<>,Stack<>,Array等。
string[] newnames = { "Tom", "Dick", "Harry", "Mary", "Jay" };
IEnumerable<string> newquery = newnames
.Where(n => n.Contains("a"))
.OrderBy(n => n.Length)
.Select(n => n.ToUpper());
foreach (string name in newquery) Console.WriteLine(name);
int[] seq1 = { 1, 2, 2, 3 };
int[] seq2 = { 3, 4, 5 };
IEnumerable<int> concat = seq1.Concat(seq2); // { 1, 2, 2, 3, 3, 4, 5 }
IEnumerable<int> union = seq1.Union(seq2); // { 1, 2, 3, 4, 5 }
子查询、创建策略和数据转换
在创建一个复杂的查询时,通常我们需要用到子查询。相信大家都记得SQL查询里的子查询,在创建LINQ查询时也是如此。在LINQ中,对于方法语法,一个子查询包含在另外一个查询的lambda表达式中,对于查询表达式语法来讲,所有不是from子句中引用的查询都是子查询。
static void TestSubQuery()
{
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
// 获取所有长度最短的名字(注意:可能有多个)
IEnumerable<string> outQuery = names
.Where(n => n.Length == names
.OrderBy(n2 => n2.Length)
.Select(n2 => n2.Length).First()); // Tom, Jay"
// 与上面方法语法等价的查询表达式
IEnumerable<string> outQuery2 =
from n in names
where n.Length ==
(from n2 in names orderby n2.Length select n2.Length).First()
select n;
// 我们可以使用Min查询运算符来简化
IEnumerable<string> outQuery3 =
from n in names
where n.Length == names.Min(n2 => n2.Length)
select n;
//可以把子查询分离出来对让它只执行一次
int shortest = names.Min(n => n.Length);
IEnumerable<string> query = from n in names
where n.Length == shortest
select n;
}
创建复杂LINQ查询的创建策略有三种:
- 渐进式创建查询,就是通过链接查询运算符的方式来创建LINQ查询。因为每一个查询运算符返回一个装饰者sequence,所以我们可以在其之上继续调用其它查询运算符。
- into关键字,是对分步构建查询表达式的一种简写方式。
- 包装查询,可以通过在一个查询中嵌入另一个查询来改写,这样可以把多个查询组合成单个查询。
考虑如下的例子:我们需要在名字列表中去除所有名字的元音字母,然后对长度大于2的名字进行排序。
// 渐进式查询(Progressive query building)
IEnumerable<string> query4 =
from n in names
select Regex.Replace(n, "[aeiou]", "");
query = from n in query where n.Length > 2 orderby n select n;
// into关键词
IEnumerable<string> query5 = from n in names
select n.Replace("a", "").Replace("e", "").Replace("i", "")
.Replace("o", "").Replace("u", "")
into noVowel
where noVowel.Length > 2
orderby noVowel
select noVowel; // Result: Dck, Hrry, Mry
// 用包装查询方式进行改写(Wrapping Queries)
IEnumerable<string> query6 =
from n1 in
(
from n2 in names
select Regex.Replace(n2, "[aeiou]", "")
)
where n1.Length > 2
orderby n1
select n1;
// 与上面等价的方法语法
IEnumerable<string> query7 = names
.Select(n => Regex.Replace(n, "[aeiou]", ""))
.Where(n => n.Length > 2)
.OrderBy(n => n);
// let关键字
var query8 = from n in names
let Vowelless = Regex.Replace(n, "[aeiou]", "")
where Vowelless.Length > 2
select n; //正是因为使用了let,此时n仍然可见
扩展方法
System.Linq.Enumerable类声明了标准查询运算符方法,即扩展了IEumerable
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
// extension methods make LINQ elegant
IEnumerable<string> query1 = 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()
);
将委托作为参数
LINQ定义了两套泛型委托类型与标准查询运算符一起使用,即Func委托和Action委托,各有17个成员。我们用做实参的委托对象必须是这些类型或这些形式之一。TR代表返回值,并且总是在类型参数列表中的最后一个。
在这里列出了前4个泛型Func委托。第一个没有方法参数,返回符合返回类型的对象。第二个接受单个方法参数并且返回一个值,依次类推。
public delegate TR Func<out TR>();
public delegate TR Func<in T1,out TR>(Tla1);
public delegate TR Func<in T1,in T2,out TR>(T1 al,T2 a2);
public delegate TR Func<in T1,in T2,in T3,out TR>( T1 al,T2 a2,T3 a3);
返回类型类型参数方法参数注意返回类型参数有一个out关键字,使之可以协变,也就是说可以接受声明的类型或从这个类型派生的类型。输人参数有一个in关键字,使之可以逆变,也就是你可以接受声明的类型或从这个类型派生的类型。
知道了这些,如果我们再来看一下Count的声明。我们可以发现第二个参数必须是委托对象,它接受单个T类型的参数作为方法参数并且返回一个bool类型的值。
public static int Count<T>(this IEumerable<T> source,
Func<T, bool>predicate);
参考:
LINQ之路
LINQ 查询简介 (C#)