演练:在 C# 中编写查询 (LINQ)

更新:2007 年 11 月

本演练将引导您学习新的 C# 3.0 语言功能,并演示如何使用这些功能来编写 LINQ 查询表达式。完成本演练后,您便可以继续学习您感兴趣的具体 LINQ 提供程序(如 LINQ to SQL、LINQ to DataSet 或 LINQ to XML)的示例和文档。

先决条件

本演练需要 Visual Studio 2008。

链接到视频 有关视频演示,请参见 Video How to: Writing Queries in C# (LINQ)(视频帮助:使用 C# 编写查询 (LINQ))。

创建 C# 项目

创建面向 .NET Framework 3.5 版的 C# 项目

  1. 启动 Visual Studio。

  2. 在“文件”菜单上指向“新建”,然后单击“项目”。

  3. 在“新建项目”对话框的右上角有三个图标。单击左边的图标并确保选中“.NET Framework 3.5 版”。

  4. 单击“Visual Studio 已安装的模板”下面的“控制台应用程序”图标。

  5. 为您的应用程序输入新的名称或接受默认名称,然后单击“确定”。

  6. 请注意,您的项目具有对 System.Core.dll 的引用以及适用于 System.Linq 命名空间的 using 指令。

创建内存中数据源

查询的数据源是 Student 对象的简单列表。每个 Student 记录都有名、姓和表示他们在班级中的测验分数的整数数组。将此代码复制到您的项目中。请注意下列特性:

  • Student 类包含自动实现的属性。

  • 列表中的每个学生都已使用对象初始值设定项进行初始化。

  • 列表本身已使用集合初始值设定项进行初始化。

将在不显式调用任何构造函数和使用显式成员访问的情况下初始化并实例化整个数据结构。有关这些新功能的更多信息,请参见自动实现的属性(C# 编程指南)对象和集合初始值设定项(C# 编程指南)

添加数据源

  • 将 Student 类和经过初始化的学生列表添加到您的项目的 Program 类中。

    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} }
    };
    

在学生列表中添加新学生

  • 将新 Student 添加到 Students 列表中并使用您选择的姓名和测验分数。尝试键入新学生的所有信息,以便更好地了解对象初始值设定项的语法。

创建查询

创建简单查询

  • 在应用程序的 Main 方法中创建一个简单查询,执行该查询时,将生成第一次测验中分数高于 90 分的所有学生的列表。请注意,由于选择了整个 Student 对象,因此查询的类型是 IEnumerable<Student>。虽然代码也可以通过使用 var 关键字来使用隐式类型,但这里使用了显式类型以便清楚地演示结果。(有关 var 的更多信息,请参见隐式类型的局部变量(C# 编程指南)。)

    另请注意,该查询的范围变量 student 用作对源中每个 Student 的引用,以提供对每个对象的成员访问。

// Create the query.
// studentQuery is an IEnumerable<Student>
var studentQuery =
    from student in students
    where student.Scores[0] > 90
    select student;

执行查询

执行查询

  1. 现在编写用于执行查询的 foreach 循环。请注意有关代码的以下事项:

    • 所返回序列中的每个元素是通过 foreach 循环中的迭代变量来访问的。

    • 此变量的类型是 Student,查询变量的类型是兼容的 IEnumerable<Student>。

  2. 添加此代码后,按 Ctrl + F5 生成并运行该应用程序,然后在“控制台”窗口中查看结果。

// Execute the query.
// var could be used here also.
foreach (Student student in studentQuery)
{
    Console.WriteLine("{0}, {1}", student.Last, student.First);
}

添加另一个筛选条件

  • 您可以在 where 子句中组合多个布尔条件,以便进一步细化查询。下面的代码添加一个条件,以便查询返回第一个分数高于 90 分并且最后一个分数低于 80 分的那些学生。where 子句应类似于以下代码。

    where student.Scores[0] > 90 && student.Scores[3] < 80
    

    有关更多信息,请参见 where 子句(C# 参考)

修改查询

对结果进行排序

  1. 如果结果按某种顺序排列,则浏览结果会更容易。您可以根据源元素中的任何可访问字段对返回的序列进行排序。例如,下面的 orderby 子句根据每个学生的姓按从 A 到 Z 的字母顺序对结果进行排序。紧靠在 where 语句之后、select 语句之前,将下面的 orderby 子句添加到您的查询中:

    orderby student.Last ascending
    
  2. 现在更改 orderby 子句,以便根据第一次测验的分数的反向顺序,即从高分到低分的顺序对结果进行排序。

    orderby student.Scores[0] descending
    
  3. 更改 WriteLine 格式字符串以便您可以查看分数:

    Console.WriteLine("{0}, {1} {2}", s.Last, s.First, s.Scores[0]);
    

    有关更多信息,请参见 orderby 子句(C# 参考)

对结果进行分组

  1. 分组是查询表达式中的强大功能。包含 group 子句的查询将生成一系列组,每个组本身包含一个 Key 和一个序列,该序列由该组的所有成员组成。下面的新查询使用学生的姓的第一个字母作为关键字对学生进行分组。

    // studentQuery2 is an IEnumerable<IGrouping<char, Student>>
    var studentQuery2 =
        from student in students
        group student by student.Last[0];
    
  2. 请注意,查询的类型现在已更改。该查询现在生成一系列将 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);
        }
    }
    
  3. 按 Ctrl + F5 运行该应用程序并在“控制台”窗口中查看结果。

    有关更多信息,请参见 group 子句(C# 参考)

隐式类型化变量

  • 显式编写 IGroupingsIEnumerables 代码很快就会变得乏味。通过使用 var,您可以更方便地编写相同的查询和 foreach 循环。var 关键字不会更改对象的类型,它仅指示编译器推断类型。将 studentQuery 和迭代变量 group 的类型更改为 var 并重新运行查询。请注意,在内部 foreach 循环中,迭代变量仍然类型化为 Student,而查询仍可像以前一样工作。将 s 迭代变量更改为 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);
        }
    }
    

    有关 var 的更多信息,请参见隐式类型的局部变量(C# 编程指南)

按键值对组进行排序

  • 当您运行前面的查询时,会注意到组没有按字母顺序排列。若要改变这种情况,您必须在 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);
        }
    }
    
    

    当您运行此查询时,就会看到组现在已按字母顺序排序。

使用 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);
    }
    

    有关更多信息,请参见 let 子句(C# 参考)

在查询表达式中使用方法语法

  • 查询语法与方法语法 (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);
    

在 select 子句中转换或投影

  1. 查询生成的序列的元素与源序列中的元素不同,这种情况很常见。删除或注释掉您之前的查询和执行循环,并用下面的代码替换它。请注意,该查询返回一个字符串(而非 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);
    }
    
    
  2. 本演练前面的代码指出班级的平均分约为 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);
    }
    
    

后续步骤

熟悉了在 C# 中使用查询的基本情况后,便可以开始阅读您感兴趣的具体类型的 LINQ 提供程序的文档和示例:

LINQ to SQL

LINQ to DataSet

LINQ to XML

LINQ to Objects

LINQ C# 示例

请参见

概念

LINQ 查询表达式(C# 编程指南)

辅助 LINQ 资源

其他资源

语言集成查询 (LINQ)

C# 中的 LINQ 入门