第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};
注:一直苦苦思索如何实现左连接呢,终于找到解了,原来这样实现的。