演练:用 C# 编写查询 (LINQ)
此演练演示用于编写 LINQ 查询表达式的 C# 语言功能。
创建 C# 项目
注意
以下说明适用于 Visual Studio。 如果使用其他开发环境,请创建包含对 System.Core.dll 的引用的控制台项目和用于 System.Linq 命名空间的 using
指令。
在 Visual Studio 中创建项目
启动 Visual Studio。
在菜单栏上,依次选择“文件” 、“新建” 、“项目” 。
“新建项目” 对话框随即打开。
依次展开“已安装”、“模板”、“Visual C#”,然后选择“控制台应用程序”。
在“名称”文本框中,输入不同的名称或接受默认名称,然后选择“确定”按钮。
新项目将出现在“解决方案资源管理器”中。
注意,此项目包含对 System.Core.dll 的引用和用于 System.Linq 命名空间的
using
指令。
创建内存中的数据源
用于查询的数据源是 Student
对象的简单列表。 每个 Student
记录都有名字、姓氏和整数数组(表示该学生在课堂上的测试分数)。 将此代码复制到项目中。 请注意下列特性:
Student
类包含自动实现的属性。列表中的每个学生都可使用对象初始值设定项进行初始化。
列表本身可使用集合初始值设定项进行初始化。
将在不显式调用任何构造函数和使用显式成员访问的情况下初始化并实例化整个数据结构。 有关这些新功能的详细信息,请参阅自动实现的属性与对象和集合初始值设定项。
添加数据源
向项目中的
Program
类添加Student
类和经过初始化的学生列表。public class Student { public string First { get; set; } public string Last { get; set; } public int ID { get; set; } public List<int> Scores; } // Create a data source by using a collection initializer. static List<Student> 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}} };
向学生列表添加新学生
- 向
Students
列表添加一个新Student
,并按自己的选择使用名称和测试分数。 尝试键入所有新学生信息,以便更好地了解对象初始值设定项的语法。
创建查询
创建简单查询
在应用程序的
Main
方法中,创建简单查询,执行该查询时,将生成所有在第一次测试中分数高于 90 的学生的列表。 注意,由于选定全部Student
对象,所以查询的类型为IEnumerable<Student>
。 尽管该代码也可以通过使用 var 关键字来使用隐式类型化,但可以使用显式类型化清楚地展示结果。 (有关var
的详细信息,请参阅隐式类型化局部变量。)另请注意,查询的范围变量
student
用作指向源中每个Student
引用,提供对每个对象的成员访问。
// Create the query.
// The first line could also be written as "var studentQuery ="
IEnumerable<Student> studentQuery =
from student in students
where student.Scores[0] > 90
select student;
执行查询
执行查询
现在,编写
foreach
循环,用于执行查询。 注意以下有关代码的注意事项:通过
foreach
循环中的迭代变量,可访问返回的序列中的每个元素。此变量的类型是
Student
,并且可与查询变量IEnumerable<Student>
的类型兼容。
添加此代码后,生成并运行应用程序,以在“控制台”窗口中查看结果。
// Execute the query.
// var could be used here also.
foreach (Student student in studentQuery)
{
Console.WriteLine("{0}, {1}", student.Last, student.First);
}
// Output:
// Omelchenko, Svetlana
// Garcia, Cesar
// Fakhouri, Fadi
// Feng, Hanying
// Garcia, Hugo
// Adams, Terry
// Zabokritski, Eugene
// Tucker, Michael
添加其他筛选条件
在
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.Last, student.First, student.Scores[0]);
有关详细信息,请参阅 orderby 子句。
对结果进行分组
分组是查询表达式中的强大功能。 包含 group 子句的查询将生成一系列组,每个组本身包含一个
Key
和一个序列,该序列由该组的所有成员组成。 以下新查询使用学生的姓的第一个字母作为关键字对学生进行分组。// studentQuery2 is an IEnumerable<IGrouping<char, Student>> var studentQuery2 = from student in students group student by student.Last[0];
注意,查询类型现已更改。 该查询现在生成一系列将
char
类型作为键的组,以及一系列Student
对象。 由于查询的类型已更改,因此以下代码也将更改foreach
执行循环:// studentGroup is a IGrouping<char, Student> foreach (var studentGroup in studentQuery2) { Console.WriteLine(studentGroup.Key); foreach (Student student in studentGroup) { Console.WriteLine(" {0}, {1}", student.Last, student.First); } } // Output: // O // Omelchenko, Svetlana // O'Donnell, Claire // M // Mortensen, Sven // G // Garcia, Cesar // Garcia, Debra // Garcia, Hugo // F // Fakhouri, Fadi // Feng, Hanying // T // Tucker, Lance // Tucker, Michael // A // Adams, Terry // Z // Zabokritski, Eugene
在“控制台”窗口中运行应用程序并查看结果。
有关详细信息,请参阅 group 子句。
对变量进行隐式类型化
IGroupings
的显式编码IEnumerables
将快速变得冗长。 使用var
可以更方便地编写相同的查询和foreach
循环。var
关键字不会更改对象的类型;它仅指示编译器推断类型。 将studentQuery
和迭代变量group
的类型更改为var
,然后重新运行查询。 注意,在内部foreach
循环中,该迭代变量仍类型化为Student
,并且查询的工作原理和以前一样。 将student
迭代变量更改为var
,然后再次运行查询。 将看到完全相同的结果。var studentQuery3 = from student in students group student by student.Last[0]; foreach (var groupOfStudents in studentQuery3) { Console.WriteLine(groupOfStudents.Key); foreach (var student in groupOfStudents) { Console.WriteLine(" {0}, {1}", student.Last, student.First); } } // Output: // O // Omelchenko, Svetlana // O'Donnell, Claire // M // Mortensen, Sven // G // Garcia, Cesar // Garcia, Debra // Garcia, Hugo // F // Fakhouri, Fadi // Feng, Hanying // T // Tucker, Lance // Tucker, Michael // A // Adams, Terry // Z // Zabokritski, Eugene
按照键值对组进行排序
运行上一查询时,会发现这些组不是按字母顺序排序的。 若要更改此排序,必须在
group
子句后提供orderby
子句。 但若要使用orderby
子句,首先需要一个标识符,用作对group
子句创建的组的引用。 可以使用into
关键字提供该标识符,如下所示:var studentQuery4 = from student in students group student by student.Last[0] into studentGroup orderby studentGroup.Key select studentGroup; foreach (var groupOfStudents in studentQuery4) { Console.WriteLine(groupOfStudents.Key); foreach (var student in groupOfStudents) { Console.WriteLine(" {0}, {1}", student.Last, student.First); } } // Output: //A // Adams, Terry //F // Fakhouri, Fadi // Feng, Hanying //G // Garcia, Cesar // Garcia, Debra // Garcia, Hugo //M // Mortensen, Sven //O // Omelchenko, Svetlana // O'Donnell, Claire //T // Tucker, Lance // Tucker, Michael //Z // Zabokritski, Eugene
运行此查询时,将看到这些组现在已按字母顺序排序。
使用 let 引入标识符
可以使用
let
关键字来引入查询表达式中任何表达式结果的标识符。 此标识符可以提供方便(如下面的示例所示),也可以通过存储表达式的结果来避免多次计算,从而提高性能。// studentQuery5 is an IEnumerable<string> // This query returns those students whose // first test score was higher than their // average score. var studentQuery5 = from student in students let 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 (string s in studentQuery5) { Console.WriteLine(s); } // Output: // Omelchenko Svetlana // O'Donnell Claire // Mortensen Sven // Garcia Cesar // Fakhouri Fadi // Feng Hanying // Garcia Hugo // Adams Terry // Zabokritski Eugene // Tucker Michael
有关详细信息,请参阅 let 子句。
在查询表达式中使用方法语法
如 LINQ 中的查询语法和方法语法 中所述,某些查询操作只能使用方法语法来表示。 以下代码为源序列中的每个
Student
计算总分,然后对该查询的结果调用Average()
方法来计算班级平均分。var studentQuery6 = from student in students let totalScore = student.Scores[0] + student.Scores[1] + student.Scores[2] + student.Scores[3] select totalScore; double averageScore = studentQuery6.Average(); Console.WriteLine("Class average score = {0}", averageScore); // Output: // Class average score = 334.166666666667
在 select 子句转换或投影
查询生成的序列的元素与源序列中的元素不同,这种情况很常见。 删除或注释掉以前的查询和执行循环,并将其替换为以下代码。 请注意,该查询将返回字符串序列,而不是
Students
,这种情况将反映在foreach
循环中。IEnumerable<string> studentQuery7 = from student in students where student.Last == "Garcia" select student.First; Console.WriteLine("The Garcias in the class are:"); foreach (string s in studentQuery7) { Console.WriteLine(s); } // Output: // The Garcias in the class are: // Cesar // Debra // Hugo
本演练中前面的代码表明班级平均分大约为 334 分。 若要生成总分数高于班级平均分的
Students
及其Student ID
的序列,可以在select
语句中使用匿名类型:var studentQuery8 = from student in students let x = student.Scores[0] + student.Scores[1] + student.Scores[2] + student.Scores[3] where x > averageScore select new { id = student.ID, score = x }; foreach (var item in studentQuery8) { Console.WriteLine("Student ID: {0}, Score: {1}", item.id, item.score); } // Output: // Student ID: 113, Score: 338 // Student ID: 114, Score: 353 // Student ID: 116, Score: 369 // Student ID: 117, Score: 352 // Student ID: 118, Score: 343 // Student ID: 120, Score: 341 // Student ID: 122, Score: 368
后续步骤
熟悉了在 C# 中使用查询的基本情况后,便可以开始阅读你感兴趣的具体类型的 LINQ 提供程序的文档和示例: