LINQ标准查询操作符

【返回目录】

上一篇文章《什么是LINQ》可能讲得太空洞、枯燥,对于编程技术类的文章来说,我个人认为还是码文并茂效果更好,有些东西理论的话讲半天不如一两行简洁的代码来得直观、明了,好在接下来的系列文章都主要以代码为主,说明性的语言为辅,我相信绝大多数阅读这些文章的朋友都是多多少少已经有了些C#编程语言的基础的,那好,咱们就言简意赅地切入正题把。

标准查询操作符就是可以查询任何.NET数组或者集合的API,这个API由System.Query.Sequence静态类中声明的方法组成。标准查询操作符遵守.NET 2.0通用语言规范并且可以用于任何支持范型的.NET编程语言。标准查询操作符可以操作所有实现了IEnumerable<T>接口的对象。

我们将采用一个假设使用场景为学校的示例来讲解标准查询操作符的使用。我们假设有这样几个类:

   1: /// <summary>
   2: /// The data class of school.
   3: /// </summary>
   4: public class SchoolData
   5: {
   6:     private int _id = -1;
   7:     public int ID
   8:     {
   9:         get { return this._id; }
  10:         set { this._id = value; }
  11:     }
  12:  
  13:     private string _name = String.Empty;
  14:     public string Name
  15:     {
  16:         get { return this._name; }
  17:         set { this._name = value; }
  18:     }
  19:  
  20:     private string _location = String.Empty;
  21:     public string Location
  22:     {
  23:         get { return this._location; }
  24:         set { this._location = value; }
  25:     }
  26:  
  27:     private string _zipCode = String.Empty;
  28:     public string ZipCode
  29:     {
  30:         get { return this._zipCode; }
  31:         set { this._zipCode = value; }
  32:     }
  33:  
  34:     private List<StudentData> _students = null;
  35:     public List<StudentData> Students
  36:     {
  37:         get { return ((this._students == null) ? this._students = new List<StudentData>() : this._students); }
  38:     }
  39: }
  40:  
  41: /// <summary>
  42: /// The data class of student.
  43: /// </summary>
  44: public class StudentData
  45: {
  46:     private int _id = -1;
  47:     public int ID
  48:     {
  49:         get { return this._id; }
  50:         set { this._id = value; }
  51:     }
  52:  
  53:     private string _name = String.Empty;
  54:     public string Name
  55:     {
  56:         get { return this._name; }
  57:         set { this._name = value; }
  58:     }
  59:  
  60:     private int _age = 0;
  61:     public int Age
  62:     {
  63:         get { return this._age; }
  64:         set { this._age = value; }
  65:     }
  66:  
  67:     private ScoreData _scores = null;
  68:     public ScoreData Scores
  69:     {
  70:         get { return this._scores; }
  71:         set { this._scores = value; }
  72:     }
  73: }
  74:  
  75: /// <summary>
  76: /// The data class of score.
  77: /// </summary>
  78: public class ScoreData
  79: {
  80:     private int _chinese = 0;
  81:     public int Chinese
  82:     {
  83:         get { return this._chinese; }
  84:         set { this._chinese = value; }
  85:     }
  86:  
  87:     private int _math = 0;
  88:     public int Math
  89:     {
  90:         get { return this._math; }
  91:         set { this._math = value; }
  92:     }
  93:  
  94:     private int _english = 0;
  95:     public int English
  96:     {
  97:         get { return this._english; }
  98:         set { this._english = value; }
  99:     }
 100: }

我们先给出将要用到的这几个数据类,现在我们得为我们的例子准备点儿原始数据:

   1: SchoolData redStarSchool
   2:     = new SchoolData {
   3:         ID = 1,
   4:         Name = "Red Star School",
   5:         Location = "Red Star Road, Beijing, China.",
   6:         ZipCode = "100000" };
   7: redStarSchool.Students.Add(
   8:     new StudentData { ID = 1,
   9:         Name = "ZeroCool",
  10:         Age = 24,
  11:         Scores = new ScoreData { Chinese = 88, Math = 91, English = 93 } });
  12: redStarSchool.Students.Add(
  13:     new StudentData {
  14:         ID = 2,
  15:         Name = "Frieda",
  16:         Age = 22,
  17:         Scores = new ScoreData { Chinese = 94, Math = 97, English = 96 } });
  18: redStarSchool.Students.Add(
  19:     new StudentData {
  20:         ID = 3,
  21:         Name = "Michael",
  22:         Age = 23,
  23:         Scores = new ScoreData { Chinese = 67, Math = 74, English = 58 } });

好了,我们实例化了一个学校数据类示例,三个学生数据类示例(略去了班级和年级的概念)以及每个学生的三门成绩。原料已经有了,接下来我们看看怎么用这些相同的原料做出不同的菜肴来吧。

首先,我们要介绍几个最基本的查询操作符:

  • from:与SQL命令中的from不同,查询表达式中的from更像是foreach,例如:
   1: from student in redStarSchool.Students
  • where:与SQL命令中的where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的子句,例如:
   1: where student.Name == "ZeroCool"
  • select:和SQL命令中的select作用相似但位置不同,查询表达式中的select及所接子句是放在表达式最后并把子句中的变量也就是结果返回回来,例如:
   1: select student;

其实细心的朋友肯定已经发现了,上面三个关键字的示例代码连起来就正好是一句完整的查询表达式了:

   1: IEnumerable<StudentData> students = from student in redStarSchool.Students
   2:                                     where student.Name == "ZeroCool"
   3:                                     select student;

这样,我们就从redStarSchool数据对象中找到了一个名为“ZeroCool”的学生的数据对象实例。至于你想用Func<TResult>委托类型或是扩展方法结合Lambda表达式的形式来翻译这段查询表达式也无所谓,关键是弄清楚查询操作符的含义与作用就行。上面的代码等效于:

   1: // Using extension method.
   2: IEnumerable<StudentData> students = redStarSchool.Students.Where(student => student.Name == "ZeroCool").Select(student => student);
   3:  
   4: // Using Func<TResult>.
   5: Func<StudentData, bool> filter = student => student.Name == "ZeroCool";
   6: Func<StudentData, StudentData> result = student => student;
   7:  
   8: IEnumerable<StudentData> students = redStarSchool.Students.Where(filter).Select(result);

接下来我们将会把精力主要放在介绍标准查询操作符而不是语法上,对语法还不太熟悉的朋友可以参考《C# 3.0 探索之旅》

  • orderby及orderby descending:对结果进行排序,默认是升序,加上descending表明采用降序,对应的扩展方法是OrderBy和OrderByDescending:
   1: IEnumerable<StudentData> students = from student in redStarSchool.Students
   2:                                     where student.Age <= 23
   3:                                     orderby student.Age descending
   4:                                     select student;
   5:  
   6: // The declaration above is equivalent to:
   7: IEnumerable<StudentData> students
   8:     = redStarSchool.Students.Where(student => student.Age <= 23).OrderByDescending(student => student.Age).Select(student => student);
  • Take:只返回限定数量的结果,在我们的数据中选择年龄小于等于23的结果应该有两个,但是我们如果只需要返回一个结果的话就可以用Take扩展方法来限定:
   1: IEnumerable<StudentData> students
   2:     = redStarSchool.Students
   3:     .Where(student => student.Age <= 23)
   4:     .OrderByDescending(student => student.Age)
   5:     .Take(1)
   6:     .Select(student => student);
  • Skip:跳过给定的数目返回后面的结果,例如我们还是查找年龄小于等于23的同学并根据讲叙排序,那么查询到的同学的名称依次是Michael和Frieda,如果我们要想跳过前面1个记录只返回后面的话(在这个例子中就是Frieda)就可以使用Skip扩展方法:
   1: IEnumerable<StudentData> students
   2:     = redStarSchool.Students
   3:     .Where(student => student.Age <= 23)
   4:     .OrderByDescending(student => student.Age)
   5:     .Skip(1)
   6:     .Select(student => student);
  • TakeWhile:该扩展方法将会用其判断条件去依次判断源序列中的元素,返回符合判断条件的元素,该判断操作将在返回false或源序列的末尾结束:
   1: IEnumerable<StudentData> students
   2:     = redStarSchool.Students
   3:     .Where(student => student.Age <= 23)
   4:     .OrderByDescending(student => student.Age)
   5:     .TakeWhile(student => student.Scores.English < 60)
   6:     .Select(student => student);
  • SkipWhile:该扩展方法和TakeWhile一样用判断条件去以此判断源序列中的元素并且跳过第一个符合判断条件的元素,一旦判断返回false,接下来将不再进行判断并返回剩下的所有元素:
   1: IEnumerable<StudentData> students
   2:     = redStarSchool.Students
   3:     .Where(student => student.Age <= 23)
   4:     .OrderByDescending(student => student.Age)
   5:     .SkipWhile(student => student.Scores.English > 60)
   6:     .Select(student => student);
  • Join:该扩展方法对两个序列中键匹配的元素进行inner join操作,例如:
   1: var students = redStarSchool.Students
   2:     .Join(redStarSchool.Students,
   3:         student => student.Age > 21,
   4:         score => score.Scores.English > 60,
   5:         (student, score) => new { student.Name, score.Scores.English });
  • GroupJoin:该扩展方法对两个序列中键匹配的元素进行grouped join操作,例如:
   1: var students = redStarSchool.Students
   2:     .GroupJoin(redStarSchool.Students,
   3:         student => student.Age < 24,
   4:         score => score.Scores.English > 60,
   5:         (student, score) => new { student.Name, count = score.Sum(s => s.Scores.English) });
  • Concat:很明显,这个扩展方法是对两个序列进行连接操作,例如:
   1: IEnumerable<string> studentNames = redStarSchool.Students
   2:     .Select(student => student.ID.ToString())
   3:     .Concat(redStarSchool.Students.Select(student => student.Name));
  • ThenBy/ThenByDescending:这两个扩展方式都是用在OrderBy/OrderByDescending之后的,第一个ThenBy/ThenByDescending扩展方法作为第二位排序依据,第二个ThenBy/ThenByDescending则作为第三位排序依据,以此类推,例如:
   1: IEnumerable<StudentData> students = redStarSchool.Students
   2:     .Where(student => student.Age > 18)
   3:     .OrderBy(student => student.Age)
   4:     .ThenBy(student => student.Scores.Chinese);
  • Reverse:该扩展方法用于将一个序列中的所有元素反向,例如:
   1: IEnumerable<StudentData> students = redStarSchool.Students
   2:     .Where(student => student.Age > 18)
   3:     .OrderBy(student => student.Age)
   4:     .Reverse();
  • GroupBy:分配并返回对传入参数进行分组操作后的可枚举对象,例如:
   1: var students = redStarSchool.Students
   2:     .GroupBy(student => student.Scores.English > 60);
  •  Distinct:该扩展方法用于查询不重复的结果集,例如:
   1: var students = redStarSchool.Students
   2:     .Select(student => student.Age)
   3:     .Distinct();
  • Union:该扩展方法将两个序列进行合并操作,例如:
   1: var students = redStarSchool.Students
   2:     .Where(student => student.Age < 24)
   3:     .Select(student => student)
   4:     .Union(redStarSchool.Students.Where(student => student.Age == 24).Select(student => student));
  • Intersect:该扩展方法先遍历第一个集合,找出所有唯一的元素,然后遍历第二个集合,并将每个元素与前面找出的元素作比对,返回所有在两个集合内都出现的元素,例如:
   1: var students = redStarSchool.Students
   2:     .Where(student => student.Age < 24)
   3:     .Select(student => student)
   4:     .Intersect(redStarSchool.Students.Where(student => student.Age < 23).Select(student => student));
  • Except:该扩展方法先遍历第一个集合,找出所有唯一的元素,然后再遍历第二个集合,返回第二个集合中所有未出现在前面所得元素集合中的元素,例如:
   1: var students = redStarSchool.Students
   2:     .Where(student => student.Age < 24)
   3:     .Select(student => student)
   4:     .Except(redStarSchool.Students.Where(student => student.Age < 23).Select(student => student));
  • AsEnumerable:该扩展方法不会改变其参数集合的类型,它只是用标准的查询操作符替换集合类型中自定义的查询操作符,例如,我们假设redStarSchool.Students是一个实现了自定义查询表达式的集合类型,而当我们想要用标准查询表达式对其进行操作时,则可以像下面代码这样:
   1: var students = redStarSchool.Students
   2:     .AsEnumerable()
   3:     .Where(student => student.Age >= 23)
   4:     .Select(student => student.Name);
  •  ToArray:该扩展方法用于将结果集转换为Array类型,例如:
   1: var students = redStarSchool.Students
   2:     .Where(student => student.Age >= 23)
   3:     .Select(student => student.Name)
   4:     .ToArray();
  • ToList:该扩展方法用于将结果集转换为List类型,例如:
   1: var students = redStarSchool.Students
   2:     .Where(student => student.Age >= 23)
   3:     .Select(student => student.Name)
   4:     .ToArray();
  • ToDictionary:该扩展方法将结果集转换为Dictionary类型,并在其参数中设置Dictionary对象的Key,例如:
   1: var students = redStarSchool.Students
   2:     .Where(student => student.Age >= 23)
   3:     .Select(student => student.Name)
   4:     .ToDictionary(student => student.ToCharArray()[0]);
  • ToLookup:该扩展方法用于从Dictionary对象中找到键与该扩展方法的参数相对应的那个键值对,常用于Join、GroupJoin和GroupBy操作返回的结果集,例如:
   1: var students = redStarSchool.Students
   2:     .Join(redStarSchool.Students, name => name.Name == "ZeroCool", age => age.Age == 22, (student, age) => new { student.Name, age.Age })
   3:     .ToLookup(name => "ZeroCool");
  • OfType:该扩展方法用于根据其参数所给定的类型来获取其所操作的数据集中的对应类型的元素,例如:
   1: var students = redStarSchool.Students
   2:     .OfType<StudentData>();
  • Cast:该扩展方法用于将所操作的集合中的元素转换成其指定的数据类型,例如:
   1: var students = redStarSchool.Students
   2:     .Select(student => student.Age)
   3:     .Cast<double>();
  • SequenceEqual:该扩展方法用于判断两个序列是否相等,如果不提供比较参数将会默认调用EqualityComparer<TSource>.Default,例如:
   1: var students = redStarSchool.Students
   2:     .Where(student => student.Age > 23)
   3:     .Select(student => student)
   4:     .SequenceEqual(redStarSchool.Students.Where(student => student.Age < 23).Select(student => student));
  • First:该扩展方法返回集合中的一个元素,例如:
   1: var student = redStarSchool.Students
   2:     .Where(classmate => classmate.Age < 24)
   3:     .Select(classmate => classmate)
   4:     .First();
  • FirstOrDefault:该扩展方法返回集合第一个元素,如果集合内没有元素则返回默认值,例如:
   1: var student = redStarSchool.Students
   2:     .Where(classmate => classmate.Age > 24)
   3:     .Select(classmate => classmate)
   4:     .FirstOrDefault();
  • Last:该扩展方法返回集合中最后一个元素,例如:
   1: var student = redStarSchool.Students
   2:     .Where(classmate => classmate.Age < 24)
   3:     .Select(classmate => classmate)
   4:     .Last();
  • LastOrDefault:该扩展方法返回集合中最后一个元素,如果集合中没有元素则返回默认值,例如:
   1: var student = redStarSchool.Students
   2:     .Where(classmate => classmate.Age > 24)
   3:     .Select(classmate => classmate)
   4:     .LastOrDefault();
  • Single:该扩展方法返回集合中一个与其指定的条件相符的元素,例如:
   1: var student = redStarSchool.Students
   2:     .Where(classmate => classmate.Age < 24)
   3:     .Single(classmate => classmate.Name.Length == 6);
  • SingleOrDefault:该扩展方法返回集合中一个与其指定的条件相符的元素,如果不存在这样的元素则返回默认值,例如:
   1: var student = redStarSchool.Students
   2:     .Where(classmate => classmate.Age > 24)
   3:     .SingleOrDefault(classmate => classmate.Name == "ZeroCool");
  • ElementAt:该扩展方法返回集合中指定索引(从0开始)处的元素,例如:
   1: var students = redStarSchool.Students
   2:     .Select(student => student)
   3:     .ElementAt(1);
  • ElementAtOrDefault:该扩展方法返回集合中指定索引处的元素,如果该元素不存在(如越界)则返回默认值,例如:
   1: var students = redStarSchool.Students
   2:     .Select(student => student)
   3:     .ElementAtOrDefault(5);
  • DefaultIfEmpty:该扩展方法为一个空集合提供一个默认元素,例如:
   1: var students = redStarSchool.Students
   2:     .Where(student => student.Age > 25)
   3:     .Select(student => student.Name)
   4:     .DefaultIfEmpty();
  • Range:该扩展方法用于获得一个整数类型的序列,例如:
   1: var range = Enumerable.Range(0, 100);
  • Repeat:该扩展方法用于获得一个重复指定次数的给定值的序列,例如:
   1: var range = Enumerable.Repeat(1, 100);
  • Empty:该扩展方法用于获得一个指定类型的空序列,例如:
   1: var range = Enumerable.Empty<Int32>();
  • Any:该扩展方法用于判断集合中是否存在满足条件的元素(若条件为空,则集合只要不为空就返回True,否则为False),例如:
   1: bool hasFailedStudent = redStarSchool.Students
   2:     .Any(student => student.Scores.Chinese < 60 || student.Scores.Math < 60 || student.Scores.English < 60);
  • All:该扩展方法用于判断集合中所有元素是否都满足条件,例如:
   1: bool allSucceed = redStarSchool.Students
   2:     .All(student => student.Scores.Chinese >= 60 && student.Scores.Math >= 60 && student.Scores.English >= 60);
  • Contains:该扩展方法判断元素中是否存在指定的元素,例如:
   1: bool zerocoolExists = redStarSchool.Students
   2:     .Where(student => student.Age <= 24)
   3:     .Select(student => student.Name)
   4:     .Contains("ZeroCool");
  • Count:该扩展方法用于统计集合中元素的个数,例如:
   1: int englishSucceedAmount = redStarSchool.Students
   2:     .Count(student => student.Scores.English > 60);
  • LongCount:对于元素个数较多的集合可视情况而选用LongCount来统计元素个数,其值为long型,例如:
   1: long studentAmount = redStarSchool.Students.LongCount();
  • Sum:该扩展方法用于对集合中数值类型元素求和,例如:
   1: int englishScoreTotal = redStarSchool.Students
   2:     .Sum(englishScore => englishScore.Scores.English);
  • Min:该扩展方法用于找出集合中数值最小的元素,例如:
   1: int lowestEnglishScore = redStarSchool.Students
   2:     .Min(englishScore => englishScore.Scores.English);
  • Max:该扩展方法用于找出集合中数值最大的元素,例如:
   1: int highestEnglishScore = redStarSchool.Students
   2:     .Max(englishScore => englishScore.Scores.English);
  • Average:该扩展方法对集合中的数值类型元素进行求平均值操作,其返回值类型为double,例如:
   1: double averageEnglishScore = redStarSchool.Students
   2:     .Average(englishScore => englishScore.Scores.English);
  • Aggregate:该扩展方法用遍历集合中的元素,并用一个种子值与当前元素通过某一指定的函数来进行对比,并保留符合条件的值,如果没有指定种子值的话,该扩展方法默认将集合的第一个元素作为种子值,例如:
   1: int youngerStudentAmount = redStarSchool.Students
   2:     .Select(age => age.Age)
   3:     .Aggregate(21, (ageA, ageB) => ageA >= ageB ? ageA : ageB);

标准查询操作符就介绍到这里了,我相信各位朋友看了这篇文章中的介绍都已经领略到了LINQ的强大与适用,相信各位朋友通过实际动手练习,很快就能轻松自如地编写查询语句,并从中获得美妙的开发体验!

posted @ 2007-11-24 21:03  麦机长  阅读(1322)  评论(2编辑  收藏  举报