博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

第11章 对象集合与LINQ

【摘要】:LINQ是.NET的一项重大创新,它可以非常方便地查询对象集合。本章主要讲述LINQ技术中最基础的部分--LINQ to Objects。

 

第1针对对象集合的标准查询

 1、 对象筛选:

A、 筛选指从某对象集合中选出满足条件的对象,通用Where扩展方法实现。

B、 方法声明:

Public static IEnumerable<Tsource> Where<Tsource>(

this IEnumerable<T> source,Func<Tsource,bool> predicate)

C、示例:
             List<FileInfo> files = ……;   
             IEnumerable<FileInfo> ret = files.Where<FileInfo>(
               file => Path.GetFileNameWithoutExtension(file.Name).Indexof(
               txtFindWhat.Text.Trim()) != -1);  

D、思考:
D1、其中file不是string 类型,而是FileInfo类型。FileInfo是.NET的类,它封装了文件的基本

信息和常用操作。

D2、也可以这样调用,和上面本质是相同的。

  IEnumerable<FileInfo> ret1 = Enumerable.Where<FileInfo>(files,

                  file => Path.GetFileNameWithoutExtension(file.Name).IndexOf(txtFindWhat.Text.Trim()) != -1);

 

2、 投影与数据转换:

A、 投影:指把某对象集合中对象的部分属性抽取出来进行处理。

B、 数据转换:指将某对象集合中感兴趣的对象(或抽取它的部分字段/属性)转换为另一种类型的对 象。

C、方法声明: public static IEnumerable<TResult> Select<Tsource,TResult>(
              this IEnumerable<Tsource> source,Func<Tsource,TResult> selector)

D、示例:

IEnumerable<string> fileList = Directory.GetFiles("c:\\", "*.*");

IEnumerable<FileInfo> files = fileList.Select(file => new FileInfo(file)); //转换

var items = fileList.Select(file =>

                    {

                        FileInfo fileInfo = new FileInfo(file);

                        return new { FileName = fileInfo.Name, Size = fileInfo.Length };

                    });

E、思考:

E1、参数file是String类型。

E2、投影与Sql的投影是类似的,你可以进行很灵活的操作。如返回的集合元素的文件名长度仅取10个字符,可改写为 return new { FileName = fileInfo.Name.Substring(0,10), Size = fileInfo.Length };

E3、这是items是隐式变量,它的类型是IEnumerable<T>,其中T为匿名类型,即 new {string FileName,long Size}。

 

3、 数据排序:

A、使用扩展方法OrderBy(升序)、OrderByDescending(降序)进行排序。

B、方法声明:

           Public static IOrderedEnumerable<Tsource> OderBy<Tsource,Tkey>(

                  This IEnumerable<Tsource> source,Func<Tsource,Tkey> keySelector)

C、示例:

          Class Pet {

                   Public string Name { get; set;}

                   Public int Age { get; set; }

                  }

          Pet[] pets = …….;

          IEnumerable<Pet> query = pets.Oderby( pet => pet.Age);

D、思考:

   D1、排序的属性必须可以比较大小的,若此属性引用一个对象,则要求此实现IComparable或Icomparable<T>接口。

   D2、返回的类型IOrderedEnumerable<Tsource>的基类型为IEnumerable<Tsource>。

4、 数据连接:

A、 数据连接:指根据某个对象的属性将两个数据集合的对象连接起来,得到一个新集合。

B、 Public static IEnumerable<TResult> Join<Tout,TInner,Tkey,TResult>{

This IEnumerable<Touter> outer,IEnumerable<Tinner> inner,

Func<Touter,Tkey> outerKeySelector,Func<Tinner,Tkey> inner KeySelector,

Func<Touter,Tinner,TResult> resultSelector

}

C、  示例:

var result = arrA.Join(                //指明连接的第一个集合对象arrA

                arrB,                 //指明连接的第二个集合对象arrB

                Aobj => Aobj.AID,     //指明第一个对象集合中的对象A的AID字段作为连接字段

                Bobj => Bobj.BID,     //指明第二个对象集合中的对象B的BID字段作为连接字段

                (Aobj, Bobj) => new  //指明结果以哪种方式返回

                {

                    Aobj.AID,

                    Aobj.AName,

                    Bobj.BName

                }

            );

D、思考:

D1、用扩展方法进行数据连接用起来太复杂,怎么办,用Linq好了,Linq简单好用。

D2、要看懂并运用此函数其实不难,只需懂得扩展方法、委托、泛型即可。

5、 对应于集合代数的标准查询:

.NET提供一组扩展方法来写成标准集合运算:

Union:并集

Concat:将两个集合“首尾相连”

Intersect: 交集

Distinct:删除集合中的重复值。

Reverse: 反转集合中的重复值。

Sum:对集合中的数累加求和。

Average:求平均值。

Max 与Min:找出最大值和最小值。

OfType:筛选出指定类型的对象。

思考

A、 Oftype的用法

  IEnumerable<object> stuff = new object[] { new object(), 1, 3, 5, 7, 9, Guid.NewGuid() };

  IEnumerable<int> odd = stuff.OfType<int>();   //筛选出整数类型。

    B、Concat 扩展方法:首尾连接后得到的集合包括重复元素。

 

第2伟大的技术创新----LINQ

 1、  概念:语言集成查询(Language-Integrated Query,LINQ)是.NET3.5引入的一项重要技术。它在标准的.NET编程语言中嵌入了一个领域特定的语言,它大大地简化了数据存取工作。

2、  意义:统一了数据存取技术,可以使用一致的方式存取多种数据源,如关系型数据、XML数据。这样不需要人工编写针对特定数据源的专用命令,而由底层的LINQ自动地进行转换。

3、  常用的Linq技术有:linq to Object、linq to DataSet、linq to SQL、linq to Entities、Linq to XML等。

4、  特性:延迟执行。

查询变量本身只是存储查询命令,实际的查询执行会延迟到foreeach语句中循环访问查询变量时发生,此即LINQ的延迟执行特性。当然,若想立即执行,可以调用IEnumerable<T>类型的ToList<Tsource>或ToArray<Tsource>扩展方法。

5、  初探LINQ to Object技术内幕

CLR并不能直接处理LINQ查询表达式,而是由C#编译器将其转化为相应的扩展方法和预定义委托。

6、  思考:

A、 何时用LINQ,何时用扩展方法?

答:前者优点:简单、方便。后者优点:灵活、强大; 对于复杂功能使用扩展方法来实现,对于简单的查询使用LINQ实现较好,当然也可混用。

 

第3掌握LINQ查询表达式的编写技巧

 1、 筛选数据:

A、任务:从C盘上查找创建日期在今天以前的纯文本(即.TXT)文件。

Ienumerabled<FileInfo> files =
  from fileName in Directory.GetFiles(“C:\\”)

     Where File.GetLastWriteTime(fileName) < DateTime.Now.Date

       && Path.GetExtension(fileName).ToUpper() == “.TXT”

          Select new FileInfo(fileName)

B、 思考:

B1、试想若不用LINQ会是多么麻烦呀!可见LINQ真是好东西。

B2、可以在select 子句中转换数据类型,动态创建新的对象。

2、 数据排序:

A、  查找C盘根目录下所有的文件,并按文件大小升序排序。

IEnumerable<string> fileName =

   From fileName in Directory.GetFiles(“C:\\”)

     Ordeby (new FileInfo(fileName)).Length,fileName ascending

       Select fileName;

B、  要点:

B1、默认为升序,若需要降序排列,将“ascending”改为“descending”。

B2、文件长度是通过将字符串 fileName 转为 FileInfo对象,然后得到其长度。

B3、编译时将Orderby子句转换为对IEnumerable<T>.OrderBy扩展方法的调用。

3、 投影:

Var files = from fileName in Directory.GetFiles(“C:\\”)

   Select new {

      Name = fileName,

      LastWriteTime = File.GetLastWriteTime(fileName)

}

4、 调用本地方法:

Private static bool IsEven(int num)  //判断是否是偶数

{

     If(num % 2)

       Return true;

     Return false;

}

Int[] numbers = {5,4,1,3,9,8,6,7,2,0};

Var queryEvenNums =

From num in numbers

   Where IsEven(num)

   Select num;

要点:

A、  由于LINQ直接与编程语言集成,因此可以在LINQ表达式中直接调用“本地方法”。

5、 嵌套使用LINQ子句:

A、  任务:在5个学生的4门课成绩单中查找至少有一门课成绩在90分以上的学生信息。

B、  代码:

Public class Student{

   Public string Name {get;set;}

   Public List<int> Scores { get;set;}   //各门课的成绩清单

List<Student> students = New List<Student>();

//向students集合中添加Student对象……

Var scoreQuery = from student in students

                 From score in student.Scores

                  Where score > 90

                   Select new {

Name =  student.Name,

score = student.Scores.Average()

};

C、  思考:

C1、何时用嵌套LINQ子句呢?

当需要处理多层次的数据时。这时用多个from子句实现。

       C2、C#编译器将多层嵌套子句的LINQ 查询转换为IEnumerable<T >.SelectMany扩展方法的调用。

  

6、 引用新的范围变量暂存查询结果:

IEnumerable<FileInfo> files =

   From fileName in Director.GetFiles(“C:\\”)

Let file = new FileInfo(fileName)

  Orderby file.Length,fileName

  Select file;

   注:引入file范围变量暂存结果,这样可以在后面的子句中直接使用此中间变量。

 

第4节 对象集合的分组与连接

 1、对象集合分组引例:

   Public class Student
   {

        Public string Name {get;set;}

        Public string City {get;set;}

}

Var studentQuery = from student in students

                     Group student by student.City;

Foreach( var studentGroup in studentQuery)   //遍历每个分组

    Console.WriteLine(“在{0}的学生清单:”,studentGroup.Key);  //输出分组标识

    //输出分组中的各个数据对象

    Foreach(Student stu in studentGroup)

         Console.WriteLine(“{0}{1}”,stu.Name,stu.City);

注:

A、  studentGroup实现了Igrouping<string,Student>接口

B、  var 真实的类型是 IEnumerable<Igrouping<string,Student>>

  

2、多段分组:

   Public static string GroupKey(List<int> Scores)

   {

        Int avg = (int) Scores.Average();

        String ret = “”;

        Switch (avg / 10){

      Case 10:

      Case 9:

            Ret = “优”; break;

      Case 8:

            Ret = “良”; break;

      Case 7:

            Ret = “中”; break;

      Case 6:

            Ret = “及格”; break;

      default:

            Ret = “不及格”; break;

}

return ret;

}

Var booleanGroupQuery = from student in students

    Group student by GroupKey(student.Scores);

注:

A、  将数据分为多组,实现多段分组。

3、内连接:

Var query = from p in people   //指定数据左集合为people

             Join r in roles   //指定数据右集合为roles

             On p.IDRole equals r.ID //指定左右集合元素的匹配属性

             Select new {

               p.Name,

               r.RoleDescription

            };

4、左外连接:

   Var query3 = from p in people  //指定数据左集合为people

                Join r in roles   //指定数据右集合为roles

                On p.IDRole equals r.id  //指定左右集合元素的匹配属性

                 From r in pr.DefaultIfEmpty(

                    //如果pr为空,则创建一个IEnumerable<Role>返回

                    //如果pr不为空,直接返回pr

                       New Role {ID=3,RoleDescription = “临时工”})

                 Select new { p.Name,r.RoleDescription};

注:一直苦苦思索如何实现左连接呢,终于找到解了,原来这样实现的。