LINQ标准查询操作符详解(转)

时间:2022-05-22 13:23:45

 一、 关于LINQ 

      LINQ 英文全称是“Language-Integrated Query”,中文为“语言集成查询”,它是微软首席架构师、Delphi 之父和C# 之父——Anders Hejlsberg 提出的并由其团队着力打造的一组用于c#和Visual Basic语言的扩展,为 C# 和 Visual Basic 语言语法提供强大的查询功能。微软从2003年开始启动LINQ的开发,在VisualStudio2008中开始加入LINQ功能。

LINQ提供的便利:

1)使用一种简化的方式编写查询语句;

2)通过消除运行时错误和捕捉编译时错误减少开发时间;

3)直接在开发语言中提供对LINQ的IntelliSense和调试支持;

4)消除关系数据和面向对象开发之间的障碍;

5)提供与数据源无关的统一查询语法。

LINQ的一大特性是可以对多种数据源查询,而且查询的语法相同,这为开发人员处理不同的数据源的数据提供了极大的便利。

LINQ 能查询的数据有但不限于:

1)对象,LINQ to Objects。比如集合、数组、字符串等等。

2)关系数据,分为 LINQ to DataSet 和 LINQ to SQL,前者用于 DataSet 查询,后者用于 SQL Server 数据库查询。

3)XML,LINQ to XML。

二、 操作符和LINQ 

下面代码演示从一个字符串数组中获取所有以“t”结尾的字符串,并打印。


string[] fruits = new string[]
                                 {
                                     "Apple", "Apricot", "Arbutus", "Banana", "Bennet", "Betelnut", "Betelnut",
                                     "Black brin"
                                 };
            IEnumerable<string> val = from fruit in fruits where fruit.EndsWith("s") select fruit;
            foreach (string s in val)
            {
                Console.WriteLine(s + "\n");
            }

这是一个简单的LINQ表达式,其中where和select是LINQ众多标准查询操作符中的两个,select是投影操作符,它在某个系列(即实现了Ienumerable<string>接口的对象上)上进行投影,where是限制操作符类型,类似我们sql语句中的where,用于过滤某个系列。局部变量fs实现了具有遍历功能的Ienumerable<string>接口。LINQ中提供了很多和where、select这样的标准查询操作符,这些查询操作符提供了很多功能,如过滤、排序、分组、聚合、转换等功能,LINQ包括两个标准查询操作符集合,一个集合用于操作IEnumerable<T>类型的对象,另外一个集合则是用于操作IQueryable<T>类型的对象。这些操作符由Enumerable和Queryable类的静态方法组成。如Enumerable类提供了Select、Where等查询操作符的静态方法,更详细的了解,可以查阅MSDN,网址:http://msdn.microsoft.com/zh-cn/library/bb341635.aspx
       
       可能有部分读者看了Enumerable和Queryable类提供了的静态方法,如Enumerable类中提供了where的方法为:

 public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate
)

在观照from fruit in fruits where fruit.EndsWith("t") select fruit这样的查询表达式会有些疑惑,因为我们通常使用静态方法都是这样用的:方法名.静态方法(方法参数),但是在上面查询表达式中,这些静态方法却被当做关键字的方式来使用。其实LINQ提供了查询语法和方法语法这两种语法来编写查询功能,像上面是查询语法,实现var fs = from fruit in fruits where fruit.EndsWith("t") select fruit这样的查询功能,我们还可以用LINQ提供的方法语法来实现:
var fs = fruits.where(f=>f.EndsWith(“t”));

可见在方法语法使用标准查询操作符和Enumerable类提供的静态方法是吻合的。写过T-SQL语句的读者对于LINQ的查询语法都很容易理解和阅读,这是人容易理解的方式,但其实CLR并不理解查询语法,CLR能够理解方法语法,因此编译一个LINQ的查询表达式时,查询语法将会被转换为方法语法。

三、 LINQ操作符类型类型

可根据标准查询操作符的操作“类型”进行分类,如把它们分成投影、限制、排序、联接、分组、串联、聚合、集合、生成、转换、元素、相等、量词、分割等。

类型 操作符名称
投影操作符 Select,SelectMany
限制操作符 Where
排序操作符 OrderBy,OrderByDescending,ThenBy,ThenByDescending,Reverse
联接操作符 Join,GroupJoin
分组操作符 GroupBy
串联操作符 Concat
聚合操作符 Aggregate,Average,Count,LongCount,Max,Min,Sum
集合操作符 Distinct,Union,Intersect,Except
生成操作符 Empty,Range,Repeat
转换操作符 AsEnumerable,Cast,OfType,ToArray,ToDictionary,ToList,ToLookup
元素操作符 DefaultIfEmpty,ElementAt,ElementAtOrDefault,First,Last,FirstOrDefault, LastOrDefault,Single,SingleOrDefault
相等操作符 SequenceEqual
量词操作符 All,Any,Contains
分割操作符 Skip,SkipWhile,Take,TakeWhile

为便于下面代码的演示,这里先定义一些公共代码。我们定义一个学生类Student,一个学校类School,然后定义一个Student的集合类Sudents和一个学校的集合schools。


/// <summary>
    /// 学生类
    /// </summary>
    public class Student
    {
        public int StuId { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public string Class { get; set; }
        public int SchoolId { get; set; }
    }     /// <summary>
    /// 学校类
    /// </summary>
    public class School
    {
        public int SchoolId { get; set; }
        public string SchoolName { get; set; }
    }
  static void Main(string[] args)
        {
             IList<School> schools = new List<School>();
            schools.Add(new School() { SchoolId = 1, SchoolName = "BeiJing Middle School " });
            schools.Add(new School() { SchoolId = 2, SchoolName = "ShangHai Middle School " });
            schools.Add(new School() { SchoolId = 8, SchoolName = "GuangZhou Middle School " });             IList<Student> students = new List<Student>();
            students.Add(new Student() { StuId = 1, Name = "Li Lei", Age = 9, Class = "Grade Three", SchoolId = 1 });
            students.Add(new Student() { StuId = 2, Name = "Han Meimei", Age = 9, Class = "Grade Three", SchoolId = 1 });
            students.Add(new Student() { StuId = 3, Name = "Li Ming", Age = 6, Class = "Grade One", SchoolId = 2 });
            students.Add(new Student() { StuId = 4, Name = "Zou Qi", Age = 7, Class = "Grade One", SchoolId = 2 });
            students.Add(new Student() { StuId = 5, Name = "Wang Long", Age = 7, Class = "Grade One", SchoolId = 1 });         }

1、投影操作符
      1) Select 将序列中的每个元素投影到新表中。
      代码演示从students数据源中查询年龄为9岁的学生,使用Select操作符从返回结果序列中返回Name和Class两列。

 var studentSelect = from s in students where s.Age == 9 select new { s.Name, s.Class };
            var studentS = students.Where(s => s.Age == 9);
            foreach (var s in studentSelect)
            {
                Console.WriteLine(s.Name + "\n");
            }

输出结果如下:

Li Lei

Han Meimei

2) SelectMany 将序列的每个元素投影到 IEnumerable<T> 并将结果序列合并为一个序列。
     比如我们需要把students集合中每位学生的姓名组成一个单词系列(一个学生名字可能有多个单词组成),则可以使用SelectMany操作符。

var studentSelectMany = students.SelectMany(s => s.Name.Split(' '));
            foreach (string s in studentSelectMany)
            {
                Console.WriteLine(s);
            }

输出结果如下:

Li
     Lei
     Han
     Meimei
     Li
     Ming
     Zou
     Qi
     Wang
     Long

 2、限制操作符
     1)Where 对序列中值进行过滤,它不启动查询的执行,也就是查询是延迟的,只有当枚举对象时where才应用到数据上

下面示例从学生集合中过滤名叫LiLei的学生,代码如下:

 IEnumerable<Student> studentWhere = students.Where(p => p.Name.Equals("Li Lei"));
            foreach (Student student in studentWhere)
            {
                Console.WriteLine(student.Name);
            }

输出结果如下:

Li Lei

上面代码建立查询语句,但这个语句并没用立即执行,而是在后面用foreach遍历stu对象时,查询语句才是执行,这就是所谓的延迟执行。LINQ的查询语句执行有两种方式,一种是延迟执行,一种是立即执行。任何返回单个值的LINQ查询都会立即执行,如我们查询学生集合中年龄7岁的学生个数:var count = (from student in students where student.Age == 7 select student).Count();

3、排序操作符
     1)OrderBy 该操作符对序列元素按照选定的键进行升序排序
     如对学生集合中的学生按照年龄进行升序排序:

 IEnumerable<Student> studentOrderBy = from student in students orderby student.Age descending select student;
            foreach (Student student in studentOrderBy)
            {
                Console.WriteLine(student.Name + " " + student.Age);
            }

输出结果如下:

Li Lei 9
     Han Meimei 9
     Zou Qi 7
     Wang Long 7
     Li Ming 6

2)OrderByDescending 这个操作符和OrderBy相反,它是对序列元素按选定的键进行降序排列

如对学生集合中的学生按照年龄进行降序序排序:

 IEnumerable<Student> studentOrderByDescending = from student in students orderby student.Age descending select student;
            foreach (Student student in studentOrderByDescending)
            {
                Console.WriteLine(student.Name + " " + student.Age);
            }

输出结果如下:

Li Lei 9
      Han Meimei 9
      Zou Qi 7
      Wang Long 7
      Li Ming 6

3)ThenBy 该操作符实现序列元素按照次关键字进行升序排序 
      如对学生集合中先按年龄排序再按姓名排序:

IEnumerable<Student> studentThenBy = students.OrderBy(p => p.Age).ThenBy(p => p.Name);
            foreach (Student student in studentThenBy)
            {
                Console.WriteLine(student.Name + "\t" + student.Age);
            }

输出结果如下:

Li Ming 6
      Wang Long       7
      Zou Qi  7
      Han Meimei      9
      Li Lei  9

我们发现ThenBy操作符并不能在查询方法中使用只能在方法语法中使用。熟悉写SQL语句的读者可能会问:IEnumerable<Student> sds = students.OrderBy(p => p.Age).ThenBy(p => p.Name);和IEnumerable<Student> sds = students.OrderBy(p => p.Age).OrderBy(p => p.Name);这两个查询语句结果有区别吗?区别是前者是在对主关键字排序结果上对那些主关键字相同的元素应用ThenBy指定的次关键字进行排序,后者是在对第一个OrderBy指定的关键字升序排序的基础上对所有的元素进行第二个OrderBy指定的关键字进行升序排序,毫无疑问,后者第一个OrderBy其实对于结果是没有意义的,因为第二个OrderBy是针对全部元素的。

 4)ThenByDecending 该操作符和ThenBy相反,它按次关键字降序排序

IEnumerable<Student> studentThenByDescending = students.OrderBy(p => p.Age).ThenByDescending(p => p.Name);
            foreach (Student student in studentThenByDescending)
            {
                Console.WriteLine(student.Name + "\t" + student.Age);
            }

输出结果如下:

Li Ming 6
      Zou Qi  7
      Wang Long       7
      Li Lei  9
      Han Meimei      9

  5)Reverse 该操作符对序列中的元素进行反转(逆序)排序
      如把学生集合类按逆序输出学生姓名:

IEnumerable<Student> studentThenReverse = students.Reverse();
            foreach (Student student in studentThenReverse)
            {
                Console.WriteLine(student.Name);
            }

输出结果如下:

Wang Long
      Zou Qi
      Li Ming
      Han Meimei
      Li Lei

  4、联接操作符
     1)Join 该操作符类似T-SQL中的inner join ,一个序列通过Join根据匹配条件联接另外一个序列。

如学生集合类根据SchoolId和学校集合类的SchoolId匹配联接,产生一个新的序列。

 var studentJoin = students.Join(schools, s => s.SchoolId, c => c.SchoolId,
                                   (s, c) => new { StudentName = s.Name, SchoolName = c.SchoolName });
            foreach (var s in studentJoin)
            {
                Console.WriteLine("StudentName:{0}\t SchoolName:{1}", s.StudentName, s.SchoolName);
            }

输出结果如下:

StudentName:Li Lei       SchoolName:BeiJing Middle School
      StudentName:Han Meimei   SchoolName:BeiJing Middle School
      StudentName:Li Ming      SchoolName:ShangHai Middle School
      StudentName:Zou Qi       SchoolName:ShangHai Middle School
      StudentName:Wang Long    SchoolName:BeiJing Middle School

2)GroupJoin 该操作符将主数据源的每一个元素和次数据源的相应的值(根据某个匹配条件)联接起来,返回一个具有层级的结果集
      
下面代码演示了打印每一所学校名称,同时打印这所学校的学生的名称,代码如下:


 var studentGroupJoin = schools.GroupJoin(students, s => s.SchoolId, c => c.SchoolId,
                                   (ss, stus) => new { SchoolName = ss.SchoolName, Students = stus.Select(p => p.Name) });
            foreach (var s in studentGroupJoin)
            {
                Console.WriteLine(s.SchoolName + "\n");
                foreach (var name in s.Students)
                {
                    Console.WriteLine("  " + name);
                }
            }

输出结果如下:

BeiJing Middle School

Li Lei
      Han Meimei
      Wang Long
      ShangHai Middle School

Li Ming
      Zou Qi
      GuangZhou Middle School

5、分组操作符
      1)GroupBy 该操作符根据一个指定的值将序列中的元素进行分组
      下面代码演示了学生集合中根据SchoolId进行分组,代码如下:


 var studentGroupBy = students.GroupBy(p => p.SchoolId);
            foreach (var st in studentGroupBy)
            {
                Console.WriteLine(st.Key + "\n");
                foreach (var s in st)
                {
                    Console.WriteLine("  " + s.Name);
                }
            }

输出结果如下:
      1

Li Lei
          Han Meimei
          Wang Long
      2

Li Ming
           Zou Qi

      6、串联操作符
      1)Concat 该操作符将两个系列合并成一个系列
      下面代码演示将students的Name和schools中SchoolName合并成一个新的系列,代码如下:

  var studentConcat = students.Select(s => s.Name).Concat(schools.Select(c => c.SchoolName));
            foreach (var st in studentConcat)
            {
                Console.WriteLine(st);             }

输出结果如下:
        Li Lei
        Han Meimei
        Li Ming
        Zou Qi
        Wang Long
        BeiJing Middle School
        ShangHai Middle School
        GuangZhou Middle School

 7、聚合操作符
        聚合操作符在一系列的值上执行特定的运算,并返回单个值,如执行求和求平均值等。
       1)Aggregate 该操作符将序列中的值进行累积并返回结果

下面演示使用Aggregate操作符返回学生集合中所有的学生名字:

 var studentAggregate = students.Select(s => s.Name).Aggregate((n, next) => n + " , " + next);
            Console.WriteLine(studentAggregate);

输出结果如下:
        Li Lei , Han Meimei , Li Ming , Zou Qi , Wang Long

2)Average 该操作符计算一个数值序列的平均值。注意它只能应用于数值数列
        下面代码演示计算学生集合中学生的平均年龄:

 var avgAge = students.Select(s => s.Age).Average();
            Console.WriteLine(avgAge);

输出结果如下:

7.6

3) Count 该操作符计算一个特定集合中元素的个数,返回结果是一个Int32类型的数值
       如计算学生集合中学生的个数:

var count = students.Count;
            Console.WriteLine(count);

输出结果如下:

5

 4)LongCount 该操作符和Count类似也是计算序列中元素的个数,所不同是返回的结果是一个Int64类型的值

 var longCount = students.LongCount();
            Console.WriteLine(longCount);

输出结果如下:

5

 5) Max 该操作符返回一个系列中的最大值。
       如计算学生集合中最大的年龄:

  var max = students.Max(p => p.Age);
            Console.WriteLine(max);

输出结果如下:

9

 6) Min 该操作符和Max相反,返回的是一个序列中的最小值。

    var min = students.Min(p => p.Age);
            Console.WriteLine(min);

输出结果如下:6

 7)Sum 该操作符计算序列中选定值的总和。
        计算学生集合中年龄的总和:

 var sum = students.Sum(p => p.Age);
            Console.WriteLine(sum);

输出结果如下:

38

8、集合操作符
      集合操作符对元素的集合或序列的集合进行操作,并返回一个集合。
      1) Distinct 该操作符删除集合中重复值,返回的结果的元素是互不相同的
      如查询学生集合中的年龄有多少种。

 var ages = students.Select(p => p.Age).Distinct();
            foreach (int age in ages)
            {
                Console.WriteLine(age);
            }

输出结果如下:
       9
       6
       7

2)Union 该操作符返回两个系列中每个互不相同的元素,即两个集合的并集∪
      我们前面提到Concat操作符,Concat是返回两个集合的所有元素,Union则是返回互不相同的元素。

如查询students的StuId和schools的SchoolId的并集,代码如下:

 var studentUnion = students.Select(s => s.StuId).Union(schools.Select(c => c.SchoolId));
            foreach (int id in studentUnion)
            {
                Console.WriteLine(id);
            }

输出结果如下:
      1  
      2
      3
      4
      5
      8

3)Intersect 该操作符返回同时存在两个系列中的元素,也就是返回两个系列元素的交集∩
      如查询students的StuId和schools的SchoolId的交集,代码如下:

var studentIntersect = students.Select(s => s.StuId).Intersect(schools.Select(c => c.SchoolId));
            foreach (int id in studentIntersect)
            {
                Console.WriteLine(id);
            }

输出结果如下:
      1
      2

 4) Except 该操作符返回的是系列A有,但系列B没有的元素
     如查询students有的编号而schools没有的编号,代码如下:

  var studentExcept = students.Select(s => s.StuId).Except(schools.Select(c => c.SchoolId));
            foreach (int id in studentExcept)
            {
                Console.WriteLine(id);
            }

输出结果如下:
     3
     4
     5

9、生成操作符
     生成操作符从现有系列的值中创建新的系列。
     1) Empty 该操作符返回一个指定类型的空的 IEnumerable<T>
     下面的代码示例演示如何使用 Empty<TResult>() 生成一个空 IEnumerable<T>。
      IEnumerable<decimal> empty = Enumerable.Empty<decimal>();
      下面的代码示例演示了 Empty<TResult>() 方法的一种可能的用法。将 Aggregate 方法应用于字符串数组的集合。仅当该数组包含四个或多个元素时,集合中的每个数组的元素被添加到结果的 IEnumerable<T> 中。Empty<TResult> 用于生成 Aggregate 的种子值,因为如果集合中没有包含四个或更多元素的数组,则只能返回空序列。


string[] names1 = { "Hartono, Tommy" };
            string[] names2 = { "Adams, Terry", "Andersen, Henriette Thaulow",
                                  "Hedlund, Magnus", "Ito, Shu" };
            string[] names3 = { "Solanki, Ajay", "Hoeing, Helge",
                                  "Andersen, Henriette Thaulow",
                                  "Potra, Cristina", "Iallo, Lucio" };             List<string[]> namesList =
                new List<string[]> { names1, names2, names3 };             IEnumerable<string> allNames =
                namesList.Aggregate(Enumerable.Empty<string>(),
                (current, next) => next.Length > 4 ? current.Union(next) : current);             foreach (string name in allNames)
            {
                Console.WriteLine(name);
            }

输出结果如下:
      Solanki, Ajay
      Hoeing, Helge
      Andersen, Henriette Thaulow
      Potra, Cristina
      Iallo, Lucio

2) Range 该操作符创建一个数字序列的集合,它包含两个参数,第一个是序列的开始值,第二个参数是产生序列的个数
      下面代码演示创建一个从1到5的数字序列,然后每个数字乘以5:

 var nums = Enumerable.Range(1, 5).Select(n => n * 5);
            foreach (int num in nums)
            {
                Console.WriteLine(num);
            }

输出结果如下:
       5
       10
       15
       20
       25

3)Repeat 该操作符创建一个但值序列,将次值重复一定的次数。
       下面代码演示创建一个包含三个元素的序列,这些元素都是“test”,代码如下:

 var str = Enumerable.Repeat("test", 3);
            foreach (string s in str)
            {
                Console.WriteLine(s);
            }

输出结果如下:
        test
        test
        test

 10、转换操作符
       1)AsEnumerable 该操作符将查询的输入以IEnumerable<Of T>类型返回,也就是可以将实现了IEnumerable<Of T>的数据源转换为IEnumerable<Of T>类型本身

许多类型都实现了IEnumerable。其中一部分还实现了与IEnumerable一模一样的公共方法。比如说,如果你有一个实现了Where方法的类型MyList(跟IEnumerable<T>一样),那么在MyList上调用Where将使用MyList的Where实现。如果先调用AsEnumerable方法然后再调用Where的话,你调用的就是IEnumerable的Where方法而不是MyList的。


  class MyList<T> : List<T>
    {         public IEnumerable<T> Where(Func<T, bool> predicate)
        {
            Console.WriteLine("调用MyList的Where方法");
            return Enumerable.Where(this, predicate);
        }
    }
pulic void AsE()
{
            MyList<string> names =
                new MyList<string> { "Li Lei", "Han Meimei", "Li Long", 
                    "Wang Ming", "Zou Qi", "Chen Fei", "Wang Tian" };             //这里调用MyList的where方法
            IEnumerable<string> query1 =
                names.Where(name => name.Contains("Li"));
            Console.WriteLine(query1.Count());             //这里隐藏了MyList的where方法,而是调用了Enmerable的Where方法。
            IEnumerable<string> query2 =
                names.AsEnumerable().Where(name => name.Contains("Li"));             Console.WriteLine(query2.Count());
}

输出结果如下:
      调用MyList的Where方法
       2
       2

2)Case 该操作符将IEnumerable集合中的元素转换为某种指定的类型。这样的好处就是通过提供必要的类型信息,可以将标准查询操作符应用在非泛型集合上

如下面代码演示通过使用Cast操作符,可以将标准操作符应用于查询ArrayList类型对象的数据源(ArrayList没有实现IEnumerable<Of T>)


 ArrayList nameList = new ArrayList();
            nameList.Add("Li Lei");
            nameList.Add("Li Ming");
            nameList.Add("Han Meimei");
            IEnumerable<string> query = nameList.Cast<string>().Where(name => name.Contains("Li"));
            foreach (string s in query)
            {
                Console.WriteLine(s);
            }

输出结果如下:
      Li Lei
      Li Ming

 3) OfType 该操作符可以对一个序列进行指定类型的过滤
      下面代码演示从ArrayList对象中过滤可以转换为Int类型的元素,代码如下:


 ArrayList nameList2 = new ArrayList();
            nameList2.Add(4);
            nameList2.Add("Li Ming");
            nameList2.Add("Han Meimei");
            nameList2.Add(1);
            nameList2.Add(2);
            IEnumerable<int> query3 = nameList2.OfType<int>();
            foreach (int i in query3)
            {
                Console.WriteLine(i);
            }

输出结果如下:
       4
       1
       2

4)ToArray 该操作符实现从一个IEnumerable序列创建一个数组。该操作符会强制查询立即执行
      下面代码演示将students集合中讲学生名字转换为一个字符串数组:

 var studentToArray = students.Select(student => student.Name).ToArray();
            Console.WriteLine(studentToArray.Count());

输出结果如下:
      5

  5)ToDictionary 该操作符将序列分别转换为一对一的Dictionary<T Key,T Value>字典
      下面代码演示将students集合中序列转换为以StuId为Key的Dictionary<Int,Student>类型的对象,代码如下:

  var studentToDictionary = students.ToDictionary(st => st.StuId);
            foreach (KeyValuePair<int, Student> keyValuePair in studentToDictionary)
            {
                Console.WriteLine("Key:{0},Name:{1}", keyValuePair.Key, keyValuePair.Value.Name);
            }

输出结果如下:
      Key:1,Name:Li Lei
      Key:2,Name:Han Meimei
      Key:3,Name:Li Ming
      Key:4,Name:Zou Qi
      Key:5,Name:Wang Long

6) ToList 该操作符将一个IEnumerable序列集合转换为List<Of T>对象,它使得查询立即执行
      下面代码将students集合中的学生姓名转换为一个类型为List<string>的字符串集合,代码如下:

List<string> studenToList = (from student in students select student.Name).ToList();
            foreach (string s in studenToList)
            {
                Console.WriteLine(s);
            }

输出结果如下:
       Li Lei
       Han Meimei
       Li Ming
       Zou Qi
       Wang Long

 7)LookUp 该操作符将序列转换为一对多的LookUp<T Key,TElement Values>字典
      如以班级名称为主键,Values为该班级的所有学生,代码如下:


 var lookUp = students.ToLookup(st => st.Class);
            foreach (var grouping in lookUp)
            {
                Console.WriteLine(grouping.Key + "\n");
                foreach (var student in grouping)
                {
                    Console.WriteLine("  " + student.Name);
                }
            }

输出结果如下:
       Grade Three

Li Lei
       Han Meimei
       Grade One

Li Ming
       Zou Qi
       Wang Long

11、元素操作符
      元素操作符从一个系列返回单个特定的元素
      1)DefaultIfEmpty 该操作符将一个空集合替换为包含默认的单个值的集合。在返回序列为空集合时且又需要返回一些对象时,可以通过该操作符返回一个默认值
      如果查询students集合中年龄为5岁的学生,因为结果为空,可以指定一个默认值。代码如下所示:

 var studentsDefaultIfEmpty = students.Where(s => s.Age == 20).DefaultIfEmpty(new Student() { Name = "默认值" });
            foreach (Student student in studentsDefaultIfEmpty)
            {
                Console.WriteLine(student.Name);
            }

输出结果如下:
      默认值

 2) ElementAt 该操作符返回集合中给定索引处的元素
      如查询students集合中索引为1的元素,代码如下:

 var studentEle = students.ElementAt(1);
            Console.WriteLine(studentEle.Name);

输出结果如下:
      Han Meimei

3)ElementAtOrDefault 该操作符返回指定索引处的元素

如果该索引超出范围,则返回默认值,如果是引用类型返回为Null,如果是值类型则返回为0。如下面获取索引为100的学生,因为索引范围没有那么大,索引返回为null。

 var studentEleD = students.ElementAtOrDefault(100);
            Console.WriteLine(studentEleD == null ? "返回默认值NULL" : studentEleD.Name);

输出结果如下:
      返回默认值null

 4)First 该操作符返回序列中的第一个元素
      如果源系列不包含任何元素,First方法将抛出一个System.InvalidOperationException
的异常。
     下面代码演示返回学生集合students的第一个元素。

  var studentFirst = students.First();
            Console.WriteLine(studentFirst.Name);

输出结果如下:
     Li Lei

 5)Last 与First操作符相反,该操作符返回序列中的最后一个元素

 var studentLast = students.Last();
            Console.WriteLine(studentLast.Name);

输出结果如下:
      Wang Long

6)FirstOrDefault 该 操作符类似First操作符,不同的是,如果没有发现任何元素则返回默认值,元素为引用类型默认值为null,值类型为0
     如查询students集合中学生年龄为20的学生的第一个,代码如下:

 var studentFirstD = students.Where(s => s.Age == 20).FirstOrDefault();
            Console.WriteLine(studentFirstD == null ? "返回默认值NULL" : studentFirstD.Name);

输出结果如下:
     返回默认值null
      7)LastOrDefault 该操作符类似Last操作符,不同的是,如果没有发现任何元素返回,则返回默认值,元素为引用类型默认值为null,值类型为0
     如查询students集合中学生年龄为20的学生的最后一个,代码如下:

var studentLastD = students.Where(s => s.Age == 20).FirstOrDefault();
            Console.WriteLine(studentFirstD == null ? "返回默认值NULL" : studentLastD.Name);

输出结果如下:
     返回默认值null

8)Single 该操作符返回系列中满足某一条件的单个元素,如果满足条件不止一个元素或零个,则抛出异常
       如获取students中名字为“Li  Lei”的学生,代码如下:

var studentSingle = students.Single(s => s.Name.Equals("Li Lei"));
            Console.WriteLine(studentSingle.Name);

输出结果如下:
       Li Lei

 9)SingleOrDefault 该操作符从一个系列中获取符合某一条件的唯一元素,如果没有符合元素,则返回默认值

元素为引用类型默认值为null,值类型默认值为0.注意如果符合条件的元素有多个,会抛出异常。
       如返回students集合中SchoolId为4的学生,代码如下:

 var studentSingleDefault = students.SingleOrDefault(s => s.SchoolId.Equals(4));
            Console.WriteLine(studentSingleDefault == null ? "返回默认值null" : studentSingleDefault.Name);

输出结果如下:
       返回默认值null

12、相等操作符
       1)SequenceEqual操作符可以判定两个集合是否相等,返回值为Boolean值

  var studentS1 = students.Where(s => s.SchoolId == 1);
            var studentS2 = students.Where(s => s.SchoolId == 1);
            Console.WriteLine(studentS1.SequenceEqual(studentS2));

输出结果如下:
        True

13.量词操作符
        1)All 该操作符判断系列的所有元素是否都满足某一条件,返回值为Boolean值
        如判断students集合中是否所有学生名字都以“L”开头,代码如下:

 bool blAll = students.All(s => s.Name.StartsWith("L"));
            Console.WriteLine(blAll);

输出结果如下:
        False

2)Any 该操作符判断集合是否至少一个元素满足某一条件,返回值为Boolean值

  bool blAny = students.Any(s => s.Name.StartsWith("L"));
            Console.WriteLine(blAny);

输出结果如下:
        True

3)Contains 该操作符判断系列是否包含某个特定的元素,返回值为Boolean
        如判断名称集合中是否包含“Li Lei”这个名称,代码如下:

 string studentName = "Li Lei";
            bool blContains = students.Select(s => s.Name).Contains(studentName);
            Console.WriteLine(blContains);

输出结果如下:
        True

  14、分割操作符
       1)Skip 该操作符能够掉到一定的元素到达一个指定位置,并且从该位置开始返回其余的元素
       如跳过students集合的前两个元素,读取剩下的元素,代码如下:

 var studentSkip = students.Skip(2);
            foreach (Student student in studentSkip)
            {
                Console.WriteLine(student.Name);
            }

输出结果如下:
       Li Ming
       Zou Qi
      Wang Long

2)SkipWhile 该操作符基于特定的谓词略过某些符合条件的元素,返回其余的元素

这里需要特别注意,SkipWhile对数据源进行枚举,从第一个枚举得到的元素开始,如果返回true,则跳过该元素,继续进行枚举操作。但是,如果一旦返回false,则该元素以后的所有元素,都不会再调用SkipWhile。

 int[] numSkipWhile = new int[] { 5, 7, 9, 6, 9, 4 };
            var numSw = numSkipWhile.SkipWhile(n => n < 9);
            foreach (int i in numSw)
            {
                Console.WriteLine(i);
            }

输出结果为:
       9
       6
       9
       4

如果数组numSkipWhile为{9,5,7,9,6,9,4},则输出结果为:
      9
      5
      7
      9
      6
      9
      4

 3)Take 该操作符返回某个系列中连续的元素子序列,子序列开始于序列的开头,结尾为某个指定的参数,如果指定的参数超过索引,则返回到系列的结尾为止

 int[] numTake = new int[] { 5, 7, 9, 6, 9, 4 };
            var numTk = numTake.Take(3);
            foreach (int i in numTk)
            {
                Console.WriteLine(i);
            }

输出结果为 :
     5
     7
     9

4) TakeWhile 该操作符通过特定的条件选取元素,如果某个元素不符合条件,则从该元素起的元素全部跳过

int[] numTaleWhile = new int[] { 5, 7, 9, 6, 9, 4 };
            var numTw = numTaleWhile.TakeWhile(n => n < 9);
            foreach (int i in numTw)
            {
                Console.WriteLine(i);
            }

输出结果为:
     5
     7