用 C# 编写查询 (LINQ)
创建内存中的数据源
用于查询的数据源是 Student 对象的简单列表。 每个 Student 记录都有名字、姓氏和整数数组(表示该学生在课堂上的测试分数)。 将此代码复制到项目中。 请注意下列特性:
Student类包含自动实现的属性。列表中的每个学生都可使用对象初始值设定项进行初始化。
列表本身可使用集合初始值设定项进行初始化。
添加数据源
- 向项目中的
Program类添加Student类和经过初始化的学生列表。
public class Student{public List<int> Scores;public string First { get; set; }public string Last { get; set; }public int ID { get; set; }}
向学生列表添加新学生
- 向
Students列表添加一个新Student,并按自己的选择使用名称和测试分数。 尝试键入所有新学生信息,以便更好地了解对象初始值设定项的语法。
创建查询
创建简单查询
- 在应用程序的
Main方法中,创建简单查询,执行该查询时,将生成所有在第一次测试中分数高于 90 的学生的列表。 注意,由于选定全部Student对象,所以查询的类型为IEnumerable<Student>。 尽管该代码也可以通过使用 var 关键字来使用隐式类型化,但可以使用显式类型化清楚地展示结果。
另请注意,查询的范围变量student用作指向源中每个Student引用,提供对每个对象的成员访问。
执行查询
执行查询
现在,编写
foreach循环,用于执行查询。 注意以下有关代码的注意事项:通过
foreach循环中的迭代变量,可访问返回的序列中的每个元素。此变量的类型是
Student,并且可与查询变量IEnumerable<Student>的类型兼容。
添加此代码后,生成并运行应用程序,以在“控制台”窗口中查看结果。
public static void Main(string[] args){var students = new List<Student>{new Student {First = "Svetlana", Last = "Omelchenko", ID = 111, Scores = new List<int> {97, 92, 81, 60}},new Student {First = "Claire", Last = "O'Donnell", ID = 112, Scores = new List<int> {75, 84, 91, 39}},new Student {First = "Sven", Last = "Mortensen", ID = 113, Scores = new List<int> {88, 94, 65, 91}},new Student {First = "Cesar", Last = "Garcia", ID = 114, Scores = new List<int> {97, 89, 85, 82}},new Student {First = "Debra", Last = "Garcia", ID = 115, Scores = new List<int> {35, 72, 91, 70}},new Student {First = "Fadi", Last = "Fakhouri", ID = 116, Scores = new List<int> {99, 86, 90, 94}},new Student {First = "Hanying", Last = "Feng", ID = 117, Scores = new List<int> {93, 92, 80, 87}},new Student {First = "Hugo", Last = "Garcia", ID = 118, Scores = new List<int> {92, 90, 83, 78}},new Student {First = "Lance", Last = "Tucker", ID = 119, Scores = new List<int> {68, 79, 88, 92}},new Student {First = "Terry", Last = "Adams", ID = 120, Scores = new List<int> {99, 82, 81, 79}},new Student {First = "Eugene", Last = "Zabokritski", ID = 121, Scores = new List<int> {96, 85, 91, 60}},new Student {First = "Michael", Last = "Tucker", ID = 122, Scores = new List<int> {94, 92, 91, 91}}};var stuQuery = from student in studentswhere student.Scores[0] > 90select student;foreach (var student in stuQuery) Console.WriteLine("{0} {1}", student.First, student.Last);Console.ReadKey();}
添加其他筛选条件
- 在
where子句中,可以组合多个布尔条件,以便进一步细化查询。 以下代码添加了一个条件,以便该查询返回第一个分数高于 90 分,并且最后一个分数低于 80 分的那些学生。where子句应与以下代码类似。
where student.Scores[0] > 90&&student.Scores[3]<80

有关详细信息,请参阅 where 子句。
修改查询
对结果进行排序
如果结果按某种顺序排列,则浏览结果会更容易。 你可以根据源元素中的任何可访问字段对返回的序列进行排序。 例如,以下
orderby子句将结果按照每个学生的姓氏以字母从 A 到 Z 的顺序排列。 将以下orderby子句添加到查询中,紧跟where语句之后、select语句之前:
orderby student.Last ascending
现在,更改
orderby子句,以便将结果根据第一次测试的分数以倒序(从最高分到最低分)的顺序排列。
orderby student.Scores[0] descending
更改
WriteLine格式字符串,以便查看分数:
Console.WriteLine("{0} {1} {2}", student.First, student.Last,student.Scores[0]);
有关详细信息,请参阅 orderby 子句。
对结果进行分组
分组是查询表达式中的强大功能。 包含 group 子句的查询将生成一系列组,每个组本身包含一个
Key和一个序列,该序列由该组的所有成员组成。 以下新查询使用学生的姓的第一个字母作为关键字对学生进行分组。
C#复制// studentQuery2 is an IEnumerable<IGrouping<char, Student>>var studentQuery2 =from student in studentsgroup student by student.Last[0];
注意,查询类型现已更改。 该查询现在生成一系列将
char类型作为键的组,以及一系列Student对象。 由于查询的类型已更改,因此以下代码也将更改foreach执行循环:foreach (var stuGroup in stuQuery2){Console.WriteLine(stuGroup.Key);foreach (Student student in stuGroup){Console.WriteLine("{0} {1} {2}",student.Last,student.First,student.Scores[0]);}}
对变量进行隐式类型化
IGroupings的显式编码IEnumerables将快速变得冗长。 使用var可以更方便地编写相同的查询和foreach循环。var关键字不会更改对象的类型;它仅指示编译器推断类型。 将studentQuery和迭代变量group的类型更改为var,然后重新运行查询。 注意,在内部foreach循环中,该迭代变量仍类型化为Student,并且查询的工作原理和以前一样。 将s迭代变量更改为var,然后再次运行查询。 将看到完全相同的结果。var stuQuery3 = from student in studentsgroup student by student.Last[0];//按照姓氏的第一个字母排序foreach (var stuGroup in stuQuery3){Console.WriteLine(stuGroup.Key);foreach (var student in stuGroup){Console.WriteLine("{0} {1} {2}",student.Last,student.First,student.Scores[0]);}}
按照键值对组进行排序
- 运行上一查询时,会发现这些组不是按字母顺序排序的。 若要更改此排序,必须在
group子句后提供orderby子句。 但若要使用orderby子句,首先需要一个标识符,用作对group子句创建的组的引用。 可以使用into关键字提供该标识符,如下所示:var stuQuery4 = from student in studentsgroup student by student.Last[0] into studentGrouporderby studentGroup.Keyselect studentGroup;//按照姓氏的第一个字母排序foreach (var stuGroup in stuQuery4){Console.WriteLine(stuGroup.Key);foreach (var student in stuGroup){Console.WriteLine("{0} {1} {2}",student.Last,student.First,student.Scores[0]);}}

运行此查询时,将看到这些组现在已按字母顺序排序。
使用 let 引入标识符
- 可以使用
let关键字来引入查询表达式中任何表达式结果的标识符。 此标识符可以提供方便(如下面的示例所示),也可以通过存储表达式的结果来避免多次计算,从而提高性能。var stuQuery5 = from student in studentslet totalScore = student.Scores[0] + student.Scores[1] + student.Scores[2] + student.Scores[3]where totalScore / 4 < student.Scores[0]select student.Last + " " + student.First;foreach (var s in stuQuery5){Console.WriteLine(s);}
在查询表达式中使用方法语法
- 如 LINQ 中的查询语法和方法语法 中所述,某些查询操作只能使用方法语法来表示。 以下代码为源序列中的每个
Student计算总分,然后对该查询的结果调用Average()方法来计算班级平均分。
var stuQuery6 = from student in studentslet totalScore = student.Scores[0] + student.Scores[1] + student.Scores[2] + student.Scores[3]select totalScore;var averageScore = stuQuery6.Average();Console.WriteLine("Class average score={0}",averageScore);Console.ReadKey();
在 select 子句转换或投影
- 查询生成的序列的元素与源序列中的元素不同,这种情况很常见。 删除或注释掉以前的查询和执行循环,并将其替换为以下代码。 请注意,该查询将返回字符串序列,而不是
Students,这种情况将反映在foreach循环中。
var stuQuery7 = from student in studentswhere student.Last == "Garcia"select student.First;Console.WriteLine("The Carcias in the class are:");foreach (string s in stuQuery7){Console.WriteLine(s);}

- 本演练中前面的代码表明班级平均分大约为 334 分。 若要生成总分数高于班级平均分的
Students及其Student ID的序列,可以在select语句中使用匿名类型:
var stuQuery7 = from student in studentslet x = student.Scores[0] + student.Scores[1] + student.Scores[2] + student.Scores[3]where x > averageScoreselect new {id = student.ID, score = x};foreach (var item in stuQuery7){Console.WriteLine("Student ID:{0},Score:{1}",item.id,item.score);}

