语言集成查询 (LINQ) 为跨各种数据源和格式处理数据提供了一种更简单的、一致的模型。初学者阅读本文最多3遍,也就差不多一天时间,便能掌握Linq。为了系统性的学习这个基础知识点,我花了近两周的时间来学习后整理本文,不过白天是没什么时间学习,晚上还要看完东方卫视两集《中国远征军》后开始学习。本次学习完全参照MSDN来进行,包括代码示例,也是学习后自己再敲打一次键盘模仿实现。
在 LINQ 查询中,您始终可以使用编程对象。而过去,查询通常用专用查询语言表示,如用于关系数据库的 SQL 和用于 XML 的 XQuery,因此,开发人员对于他们查询的每种类型的数据源或数据格式,都不得不学习一种新的查询语言。
创建查询后必须执行该查询以检索任何数据。在 LINQ 中,查询存储在变量中。可以指定返回信息之前信息的排序、分组和表现方式。
可以通过两种不同的语法编写 LINQ to Entities 查询:查询表达式语法和基于方法的查询语法。 查询表达式语法是 C# 3.0 和 Visual Basic 9.0 中的新增功能,它由一组用类似于 Transact-SQL 或 XQuery 的声明性语法所编写的子句组成。 不过,.NET Framework 公共语言运行时 (CLR) 无法读取查询表达式语法本身。 因此,在编译时,查询表达式将转换为 CLR 能理解的形式,即方法调用。 这些方法称为“标准查询运算符”。 作为开发人员,您可以选择使用方法语法而不使用查询语法直接调用这些方法。
接下来要分别学习查询表达式语法与基于方法的查询语法,有可能要安排时间学习一下C#3.0的基本语法。
1 LINQ 查询简介
所有 LINQ 查询操作都由以下三个不同的操作组成: 1.获取数据源;2.创建查询;3.执行查询。
class IntroToLINQ
{
static void Main()
{
// 1. Data source.
int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery = from num in numbers where (num % 2) == 0 select num;
// 3. Query execution.
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}
}
}
1.1 数据源
数据源必须是支持 IEnumerable<T> 或派生接口(如泛型 IQueryable<T>)类型的“可查询类型”。使用 LINQ 可以从 SQL Server 数据库、XML、内存中数组和集合、ADO.NET 数据集或任何其他支持 LINQ 的远程或本地数据源查询数据。
以下是一个ArrayList做为数据源查询的例子:
public class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int[] Scores { get; set; }
}
ArrayList arrList = new ArrayList();
arrList.Add( new Student {
FirstName = "Svetlana", LastName = "Omelchenko", Scores = new int[] { 98, 92, 81, 60 }
});
arrList.Add( new Student {
FirstName = "Claire", LastName = "O’Donnell", Scores = new int[] { 75, 84, 91, 39 }
});
var query = from Student student in arrList where student.Scores[0] > 95 select student;
foreach (Student s in query){
Console.WriteLine(s.LastName + ": " + s.Scores[0]);
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
1.2 查询
查询指定要从数据源中检索的信息。 查询还可以指定在返回这些信息之前如何对其进行排序、分组和结构化。 查询存储在查询变量中,并用查询表达式进行初始化。 为使编写查询的工作变得更加容易,C# 引入了新的查询语法。
查询表达式包含三个子句:from、where 和 select。 from 子句指定数据源,where 子句应用筛选器,select 子句指定返回的元素的类型。
1.3 查询执行
1.3.1 延迟执行
如前所述,查询变量本身只是存储查询命令。 实际的查询执行会延迟到在 foreach 语句中循环访问查询变量时发生。 此概念称为“延迟执行”。
1.3.2 强制立即执行
对一系列源元素执行聚合函数的查询必须首先循环访问这些元素。 Count、Max、Average 和 First 就属于此类查询。 由于查询本身必须使用 foreach 以便返回结果,因此这些查询在执行时不使用显式 foreach 语句。 另外还要注意,这些类型的查询返回单个值,而不是 IEnumerable 集合。 下面的查询返回源数组中偶数的计数:
var evenNumQuery =
from num in numbers
where (num % 2) == 0
select num;
int evenNumCount = evenNumQuery.Count();
List<int> numQuery2 = (from num in numbers where (num % 2) == 0 select num).ToList();
// or like this:
// numQuery3 is still an int[]
var numQuery3 = (from num in numbers where (num % 2) == 0 select num).ToArray();
2 查询表达式语法
查询表达式是一种声明性查询语法。 通过这一语法,开发人员可以使用类似于 Transact-SQL 的高级语言格式编写查询。 通过使用查询表达式语法,您可以用最少的代码对数据源执行复杂的筛选、排序和分组操作。
建议尽量使用表达式语法,只在必需的情况下才使用方法语法。 这两种不同形式在语义或性能上没有区别。 查询表达式通常比用方法语法编写的等效表达式更易读。
“查询表达式”是用查询语法表示的查询, 是一流的语言构造。 它就像任何其他表达式一样,并且可以用在 C# 表达式有效的任何上下文中。 查询表达式由一组用类似于 SQL 或 XQuery 的声明性语法编写的子句组成。 每个子句又包含一个或多个 C# 表达式,而这些表达式本身又可能是查询表达式或包含查询表达式。
2.1 查询表达式基础
“查询”是指一组指令,这些指令描述要从一个或多个给定数据源检索的数据以及返回的数据应该使用的格式和组织形式。从应用程序的角度来看,原始源数据的具体类型和结构并不重要。 应用程序始终将源数据视为一个 IEnumerable<T> 或 IQueryable<T> 集合。 在 LINQ to XML 中,源数据显示为一个 IEnumerable<XElement>。 在 LINQ to DataSet 中,它是一个 IEnumerable<DataRow>。 在 LINQ to SQL 中,它是您定义用来表示 SQL 表中数据的任何自定义对象的 IEnumerable 或 IQueryable。
查询表达式必须以 from 子句开头,并且必须以 select 或 group 子句结尾。 在第一个 from 子句和最后一个 select 或 group 子句之间,查询表达式可以包含一个或多个下列可选子句:where、orderby、join、let 甚至附加的 from 子句。 还可以使用 into 关键字使 join 或 group 子句的结果能够充当同一查询表达式中附加查询子句的源。
看下列一组示例(假定 scores 是 int[]),理解查询的三项主要工作。
1、检索一个元素子集以产生一个新序列。
IEnumerable<int> highScoresQuery =
from score in scores where score > 80 orderby score descending select score;
2、检索一个元素序列,但是将这些元素转换为具有新类型的对象。
IEnumerable<string> highScoresQuery2 =
from score in scores where score > 80 orderby score descending
select String.Format("The score is {0}", score);
3、检索有关源数据的单一值,例如: 符合某个条件的元素的数量; 具有最大值或最小值的元素;符合某个条件的第一个元素,或一组指定元素中的特定值之和。 例如,下面的查询从 scores 整数数组中返回高于 80 的分数的数量。
int highScoreCount = (from score in scores where score > 80 select score) .Count();或者:
IEnumerable<int> highScoresQuery3 = from score in scores where score > 80 select score;
int scoreCount = highScoresQuery3.Count();
查询变量隐式类型化:
var queryCities = from city in cities where city.Population > 100000 select city;
当源序列中的每个元素本身就是集合或包含集合时,可使用附合from 子句。 例如,假定您具有一个 Country 对象集合,而其中每个对象都包含一个名为 Cities 的 City 对象集合。 若要查询每个 Country 中的 City 对象,请使用两个from 子句,如下所示:
IEnumerable<City> cityQuery = from country in countries from city in country.Cities
where city.Population > 10000 select city;
2.1.1 group 子句
group 子句返回一个 IGrouping<TKey, TElement> 对象序列,这些对象包含零个或更多个与该组的键值匹配的项。
如:var studentQuery1 = from student in students group student by student.Last[0]; //完整的姓氏首字母。
by用于指定如何对返回的项进行分组,并指定分组的键(IGrouping的Key)。
如果您想要对每个组执行附加查询操作,则可以使用 into 上下文关键字指定一个临时标识符。 使用 into 时,必须继续编写该查询,并最终用一个 select 语句或另一个 group 子句结束该查询,如下面的代码摘录所示:
var studentQuery2 = from student in students group student by student.Last[0]
into g orderby g.Key select g;
2.1.1.1 枚举组查询的结果
由于 group 查询产生的 IGrouping<TKey, TElement> 对象实质上是列表的列表,因此必须使用嵌套的 foreach 循环来访问每一组中的各个项。 外部循环用于循环访问组键,内部循环用于循环访问组本身中的每个项。 组可能具有键,但没有元素。 以下是执行上述代码示例中的查询的 foreach 循环:
foreach (IGrouping<char, Student> studentGroup in studentQuery2)
{
Console.WriteLine(studentGroup.Key);
// Explicit type for student could also be used here.
foreach (var student in studentGroup)
{
Console.WriteLine(" {0}, {1}", student.Last, student.First);
}
}
2.1.1.2 键类型
组键可以是任何类型,如字符串、内置数值类型、用户定义的命名类型或匿名类型。
1、为指定字符串键。
var studentQuery3 = from student in students group student by student.Last;// 完整的姓氏
2、按布尔进行分组。
List<Student> students = GetStudents();
// Group by true or false.
// Query variable is an IEnumerable<IGrouping<bool, Student>>
var booleanGroupQuery =from student in students group student by student.Scores.Average() >= 80; //pass or fail!
// Execute the query and access items in each group
foreach (var studentGroup in booleanGroupQuery)
{
Console.WriteLine(studentGroup.Key == true ? "High averages" : "Low averages");
foreach (var student in studentGroup)
{
Console.WriteLine(" {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
}
}
2.1.1.3 按复合键进行分组
当您想要按照多个键对元素进行分组时,可使用复合键。 通过使用匿名类型或命名类型来存储键元素,可以创建复合键。
在下面的示例中,假定已经使用名为 surname 和 city 的两个成员声明了类 Person。 group 子句使得为每组具有相同姓氏和相同城市的人员创建一个单独的组。
group person by new {name = person.surname, city = person.city};
2.1.1.4 使用into附加查询
此示例演示在创建组之后,通过 into 实现的延续对这些组执行附加逻辑查询。
static void Main()
{
// Create the data source.
string[] words2 = { "blueberry", "chimpanzee", "abacus", "banana", "apple", "cheese", "elephant", "umbrella", "anteater" };
// Create the query.
var wordGroups2 = from w in words2 group w by w[0] into grps
where (grps.Key == 'a' || grps.Key == 'e' || grps.Key == 'i'
|| grps.Key == 'o' || grps.Key == 'u')
select grps;
// Execute the query.
foreach (var wordGroup in wordGroups2)
{
Console.WriteLine("Groups that start with a vowel: {0}", wordGroup.Key);
foreach (var word in wordGroup)
{
Console.WriteLine(" {0}", word);
}
}
// Keep the console window open in debug mode
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
2.1.2 Into
可以使用 into 上下文关键字创建一个临时标识符,以便将 group、join 或 select 子句的结果存储到新的标识符中。 此标识符本身可以是附加查询命令的生成器。 在 group 或 select 子句中使用新标识符的用法有时称为“延续”。
2.1.3 let 子句
在查询表达式中,存储子表达式的结果有时很有用,这样可以在随后的子句中使用。该关键字可以创建一个新的范围变量,用在From子句之后,通常用于Where、Group子句,如果该范围变量存储的是可查询的类型,还可以对其进行查询。
string[] strings = {
"A penny saved is a penny earned.", "The early bird catches the worm.", "The pen is mightier than the sword." };
var earlyBirdQuery =
from sentence in strings let words =sentence.Split(' ')
from word in words let w = word.ToLower()
where w[0] == 'a' || w[0] == 'e'|| w[0] == 'i' || w[0] == 'o' || w[0] == 'u'
select word;
/* Output:
"A" starts with a vowel
"is" starts with a vowel
"a" starts with a vowel
"earned." starts with a vowel
"early" starts with a vowel
"is" starts with a vowel
*/
var studentQuery = from student in students let avg = (int)student.Scores.Average()
group student by (avg == 0 ? 0 : avg / 10) into g orderby g.Key select g;
2.1.4 复合 from 子句
在某些情况下,源序列中的每个元素本身可能是序列,也可能包含序列。 例如,数据源可能是一个 IEnumerable<Student>,其中,序列中的每个 Student 对象都包含一个测验得分列表。 若要访问每个 Student 元素中的内部列表,可以使用复合 from 子句。 该技术类似于使用嵌套的 foreach 语句。 可以向任一 from 子句中添加 where 或 orderby 子句来筛选结果。 下面的示例演示了一个 Student 对象序列,其中每个对象都包含一个表示测验得分的内部整数 List。 为了访问该内部列表,此示例使用了复合 from 子句。 如有必要,可在两个 from 子句之间再插入子句。
public class Student
{
public string LastName { get; set; }
public List<int> Scores {get; set;}
}
var scoreQuery = from student in students from score in student.Scores
where score > 90 select new { Last = student.LastName, score };
2.1.4.1 组内分组
应用复合from子句实现嵌套分组:
var queryNestedGroups =
from student in students
group student by student.Year into newGroup1
from newGroup2 in
(from student in newGroup1
group student by student.LastName)
group newGroup2 by newGroup1.Key;
可参见查询对象集合示例:QueryObjectSetSample.zip。
2.1.5 使用多个 from 子句的行联接
复合 from 子句用于访问单个数据源中的内部集合。不过,查询还可以包含多个可从独立数据源生成补充查询的 from 子句。 使用此技术可以执行某些类型的、无法通过使用 join 子句执行的联接操作。下面的示例演示如何使用两个 from 子句构成两个数据源的完全交叉联接。
char[] upperCase = { 'A', 'B', 'C' };
char[] lowerCase = { 'x', 'y', 'z' };
var joinQuery1 = from upper in upperCase
from lower in lowerCase select new { upper, lower };
var joinQuery2 = from lower in lowerCase where lower != 'x'
from upper in upperCase
select new { lower, upper };
Console.WriteLine("Cross join:");
foreach (var pair in joinQuery1)
{
Console.WriteLine("{0} is matched to {1}", pair.upper, pair.lower);
}
Console.WriteLine("Filtered non-equijoin:");
foreach (var pair in joinQuery2)
{
Console.WriteLine("{0} is matched to {1}", pair.lower, pair.upper);
}
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
输入结果如下图:
1.1.1 From与Group示例
查询对象集合示例:QueryObjectSetSample.zip
1.1.2 join 子句
1.1.2.1 内联
内部同等联接。
var innerJoinQuery =from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { ProductName = prod.Name, Category = category.Name };
1.1.2.2 分组联接
含有 into 表达式的 join 子句称为分组联接。分组联接会产生一个分层的结果序列,该序列将左侧源序列中的元素与右侧源序列中的一个或多个匹配元素相关联。如果在右侧源序列中找不到与左侧源中的元素相匹配的元素,则 join 子句会为该项产生一个空数组。 因此,分组联接基本上仍然是一种内部同等联接,区别只在于分组联接将结果序列组织为多个组。
var innerGroupJoinQuery = from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select new { CategoryName = category.Name, Products = prodGroup };
还可以将分组联接的结果用作其他子查询的生成器:
var innerGroupJoinQuery2 =from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from prod2 in prodGroup where prod2.UnitPrice > 2.50M
select prod2;
1.1.2.3 左外联
若要在 LINQ 中执行左外部联接,需要将 DefaultIfEmpty 方法与组联接结合起来,以指定要在某个左侧元素不具有匹配元素时产生的默认右侧元素。
var leftOuterJoinQuery =from category in categories orderby category.ID
join prod in products on category.ID equals prod.CategoryID into prodGroup
from item in prodGroup.DefaultIfEmpty(new Product{Name = String.Empty, CategoryID = 0})
select new { CatName = category.Name, ProdName = item.Name };
1.1.2.4 使用复合键进行联接
想要使用多个键来定义匹配的联接操作,使用组合键来完成此操作。 以匿名类型或包含要比较的值的命名类型的形式来创建组合键。 如果将跨方法边界传递查询变量,请使用为该键重写 Equals 和 GetHashCode 的命名类型。 属性的名称以及属性出现的顺序在每个键中必须相同。
var query = from o in db.Orders
from p in db.Products
join d in db.OrderDetails
on new {o.OrderID, p.ProductID} equals new {d.OrderID, d.ProductID} into details
from d in details
select new {o.OrderID, p.ProductID, d.UnitPrice};
1.1.2.5 非同等联接
一些情况下无法使用 join 子句:1、联接是在不等式(非同等联接)上断言的。2、联接是在多个等式或不等式上断言的。3、必须在联接操作之前为右侧(内部)序列引入一个临时范围变量。
若要执行非同等联接,可以使用多个 from 子句单独引入每个数据源。然后,在 where 子句中将谓词表达式应用于每个源的范围变量。 该表达式还可以采用方法调用的形式。
例如,一个交叉联:
var crossJoinQuery = from c in categories
from p in products
select new { c.ID, p.Name };
Console.WriteLine("Cross Join Query:");
foreach (var v in crossJoinQuery)
{
Console.WriteLine("{0,-5}{1}", v.ID, v.Name);
}
下面示例相当于左联接,列出存在产品的类别下的所有产品:
var nonEquijoinQuery = from p in products
let catIds = from c in categories
select c.ID
where catIds.Contains(p.CategoryID) == true// Contains判断是否存在
select new { Product = p.Name, CategoryID = p.CategoryID };
1.1.3 Join示例
1.1.4 在查询表达式中处理 Null 值
如果源集合为 null 或包含值为 null 的元素,并且查询未处理 null 值,当您执行查询时将会引发 NullReferenceException。
可以采用防御方式进行编码以避免 null 引用异常,如下面的示例中所示:
var query1 =from c in categories where c != null
join p in products on c.ID equals (p == null ? null : p.CategoryID)
select new { Category = c.Name, Name = p.Name };
1.1.4.1 可以为 null 的类型
可以为 null 的类型可以表示其基础值类型正常范围内的值,再加上一个 null 值。例如,Nullable<Int32> 读作“可以为 null 的 Int32”,可以将其赋值为 -2147483648 到 2147483647 之间的任意值,也可以将其赋值为 null 值。
可以为 null 的类型表示可被赋值为 null 值的值类型变量。语法 T? 是 Nullable<T> 的简写,此处的 T 为值类型。为可以为 null 的类型赋值的方法与为一般值类型赋值的方法相同,如 int? x = 10;或 double? d = 4.108. 对于可以为 null 的类型,也可向其赋 null 值: int? x = null.
对可以为 null 的类型,还可以使用 == 和 != 运算符,例如 if (x != null) y = x;
2 基于方法的查询语法
基于方法的查询语法是一系列针对 LINQ 运算符方法的直接方法调用,同时将 lambda 表达式作为参数传递。http://msdn.microsoft.com/zh-cn/library/bb397687(v=VS.90).aspx
2.1 匿名方法
匿名方法是指将一段代码块作为委托参数传递给另外一个方法中。匿名方法能访问方法中的变量和类中的成员。使用匿名方法,则不必创建当独的方法,因此减少了实例化委托所需的编码开销。
在 2.0 之前的 C# 版本中,声明委托的唯一方法是使用命名方法。C# 2.0 引入了匿名方法,而在 C# 3.0 及更高版本中,Lambda 表达式取代了匿名方法,作为编写内联代码的首选方式。但匿名方法提供了 Lambda 表达式中所没有的功能。匿名方法使您能够省略参数列表,这意味着可以将匿名方法转换为带有各种签名的委托。这对于 Lambda 表达式来说是不可能的。
要将代码块传递为委托参数,创建匿名方法则是唯一的方法。
如:button1.Click += delegate(System.Object o, System.EventArgs e)
{ System.Windows.Forms.MessageBox.Show("Click!"); };//代码块传递为委托参数
通过使用匿名方法,由于您不必创建单独的方法,因此减少了实例化委托所需的编码系统开销。例如,如果创建方法所需的系统开销是不必要的,则指定代码块(而不是委托)可能非常有用。启动新线程即是一个很好的示例。无需为委托创建更多方法,线程类即可创建一个线程并且包含该线程执行的代码。
void StartThread()
{
System.Threading.Thread t1 = new System.Threading.Thread
(delegate()
{
System.Console.Write("Hello, ");
System.Console.WriteLine("World!");
});
t1.Start();}
2.2 泛型委托
Func<(Of <(T, TResult>)>) 泛型委托:封装一个具有一个参数并返回 TResult 参数指定的类型值的方法。如果想增加参数可以写成Func<(Of <(T1,T2, TResult>)>) 等。这种方法比起传统的显示声明委托的方法从代码结构上要简化不少,我们不用特意去申请一个delegate,所有的委托都可以用泛型委托来代替。
例如:现在求2个int类型的和
(1) 原始的方法
public int Add(int a, int b)
{
return a + b;
}
(2)通过委托来实现
delegate int deleAdd(int a, int b);
public void DeleConcrete()
{
deleAdd deleadd= Add;
deleadd(1, 2);
}
(3)直接通过泛型委托Func来实现
public int GetFunc(int a,int b)
{
Func<int, int, int> add = Add;
return add(a, b);
}
2.3 表达式目录树
表达式目录树以数据形式表示语言级别代码。数据存储在树形结构中。表达式目录树中的每个节点都表示一个表达式,例如一个方法调用或诸如 x < y 的二元运算。
下面的插图显示一个表达式及其表达式目录树形式的表示形式的示例。表达式的不同部分进行了颜色编码,以便与表达式目录树中相应的表达式目录树节点匹配。此外,还显示了不同类型的表达式目录树节点。
System.Linq.Expressions 命名空间提供用于手动生成表达式目录树的 API。编译器也可以为您生成表达式目录树。编译器生成的表达式目录树的根始终在类型 Expression<TDelegate> 的节点中;也就是说,其根节点表示一个 lambda 表达式。
表达式目录树是不可变的。这意味着,如果需要修改某个表达式目录树,则必须通过复制现有的表达式目录树并对其进行修改来构造一个新的表达式目录树。您可以使用表达式目录树访问器来遍历现有表达式目录树。
在将 lambda 表达式分配给 Expression<TDelegate> 类型的变量时,编译器将发出一个表示 lambda 表达式的表达式目录树。例如,在 Queryable 类中定义的某些标准查询运算符方法包含 Expression<TDelegate> 类型的参数。当您调用这些方法时,可以传入 lambda 表达式,然后编译器会生成表达式目录树。
1.1 Lambda 表达式
“Lambda 表达式”是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式目录树类型。
所有 Lambda 表达式都使用 Lambda 运算符 =>,该运算符读为“goes to”。该 Lambda 运算符的左边是输入参数(如果有),右边包含表达式或语句块。Lambda 表达式 x => x * x 读作“x goes to x times x”。
可以将此表达式分配给委托类型,如下所示:
delegate int del(int i);
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25
创建表达式目录树类型:
Expression<del> = x => x * x;
=> 运算符具有与赋值运算符 (=) 相同的优先级,并且是右结合运算符。
Lambda 用在基于方法的 LINQ 查询中,作为诸如 Where 和 Where 等标准查询运算符方法的参数。使用基于方法的语法在 Enumerable 类中调用 Where 方法时,参数是委托类型 System.Func<T, TResult>。使用 Lambda 表达式创建委托最为方便,同样,Lambda 表达式也是一种用于构造表达式目录树的非常简练的方式。
在 is 或 as 运算符的左侧不允许使用 Lambda。适用于匿名方法的所有限制也适用于 Lambda 表达式。
1.1.1 Lambda 表达式
在=> 运算符右边的 部分称为“Lambda 表达式”。
基本形式:(input parameters) => expression
使用空括号指定零个输入参数:
() => SomeMethod()
两个或更多输入参数由括在括号中的逗号分隔:
(x, y) => x == y
有时,编译器难于或无法推断输入类型。如果出现这种情况,您可以按以下示例中所示方式显式指定类型:
(int x, string s) => s.Length > x
1.1.2 Lambda 语句
Lambda 语句与 Lambda 表达式类似,只是语句括在大括号中:
(input parameters) => {statement;}
Lambda 语句的主体可以包含任意数量的语句;但是,实际上通常不会多于两个或三个语句。像匿名方法一样,Lambda 语句无法用于创建表达式目录树。
1.1.3 在查询表达式的方法调用中使用 Lambda 表达式
下面的示例演示如何在查询表达式的方法调用中使用 Lambda 表达式。Lambda 是必需的,因为无法使用查询语法来调用 Sum() 标准查询运算符。查询首先按 GradeLevel 枚举中定义的方式,依据学生的成绩等级对学生进行分组。然后,对于每个组,查询将添加每名学生的总分。这需要两个 Sum 运算。内部的 Sum 计算每名学生的总分,外部的 Sum 保留该组中所有学生的运行合并总计。
var categories =
from student in students
group student by student.Year into studentGroup
select new { GradeLevel = studentGroup.Key, TotalScore = studentGroup.Sum(s => s.ExamScores.Sum()) };
2 附word版