从LINQ开始之LINQ to Objects(下)
前言
上一篇《从LINQ开始之LINQ to Objects(上)》主要介绍了LINQ的体系结构、基本语法以及LINQ to Objects中标准查询操作符的使用方法。
本篇则主要讨论LINQ to Objects中的扩展方法以及延迟加载等方面的内容。
扩展方法
扩展方法简介
扩展方法能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或其他方式修改原始类型。扩展方法是静态方法,它是类的一部分,但实际没有放在类的源代码当中。
下面,我们来看一个简单示例,为上一篇中定义的Employee类添加扩展方法GetSeniority获取员工在本公司的工龄:
public static class EmployeeExtension
{/// <summary>/// 计算员工在本公司的工龄/// </summary>/// <param name="employee"></param>/// <returns></returns>public static long GetSeniority(this Employee employee){TimeSpan ts = DateTime.Now - employee.EntryDate;return (long)ts.TotalDays / 365;}
}
接下来,遍历employees列表,输出所有员工的姓名及工龄:
//获取所有员工的姓名及在本公司的工龄foreach (var employee in employees){Console.WriteLine("EmployeeName: " + employee.EmployeeName + " Seniority: " + employee.GetSeniority());}//******************************Output*******************************//EmployeeName: Mike Seniority: 1//EmployeeName: Jack Seniority: 10//EmployeeName: Adolph Seniority: 0//EmployeeName: Antony Seniority: 6//EmployeeName: Asa Seniority: 2//EmployeeName: Bernie Seniority: 9//EmployeeName: Carl Seniority: 2//EmployeeName: Duncan Seniority: 7//EmployeeName: Aimee Seniority: 0//EmployeeName: Cassie Seniority: 3//*******************************************************************
由示例可以看出:
1)扩展方法中,可以访问被扩展类型的所有公有方法和属性。
2)第一个参数是要扩展的类型,以this关键字开头。
3)即使扩展方法是静态的,也要使用标准的实例方法语法进行调用。
下面的示例演示了如果扩展方法与类中的某个方法具有相同的签名,则扩展方法不会被调用。在Employee类中定义方法SayHello
public void SayHello(){Console.WriteLine("Hello , I'm " + EmployeeName);}
在EmployeeExtension类中为Employee类定义扩展方法SayHello
public static void SayHello(this Employee employee){Console.WriteLine("Hello , I'm " + employee.EmployeeName + " ,this is Extension Method");}
此时,新入职了一位同事Dave,调用SayHello方法向大家问好
Employee dave = new Employee("011", "Dave", 30, new DateTime(2017, 5, 25), Sex.Male, Department.PD, 200000, new string[] { "climbing" });dave.SayHello();//******************************Output*******************************//Hello , I'm Dave//*******************************************************************
注意:此时调用的是Employee类下面的SayHello方法。
使用扩展方法来扩展接口
把方法扩展到某个接口中,实现该接口的多个类就可以使用相同的实现代码。
以下示例介绍了扩展方法扩展接口的使用场景,首先,定义了一个接口IHobby,接口中包含Play方法
public interface IHobby
{void Play();
}
分别创建类Reading、Swimming、Shopping实现IHobby接口
public class Reading : IHobby
{public void Play(){Console.WriteLine("I'm Reading.");}
}public class Swimming : IHobby
{public void Play(){Console.WriteLine("I'm Swimming.");}
}public class Shopping : IHobby
{public void Play(){Console.WriteLine("I'm Shopping.");}
}
此时,我们需要在实现IHobby接口的类增加一个的方法ShareFeelings,输出I'm happpy.当然,可以在接口上新增一个方法,然后将实现该接口的类逐个添加ShareFeelings方法,假如实现该接口的类很多,使用扩展方法,就可以大大的减少代码的修改量,测试起来也非常简单。
public static void ShareFeelings(this IHobby hobby){Console.WriteLine("I'm happy.");}
使用接口变量来调用扩展方法
IHobby hobby = new Reading();hobby.ShareFeelings();//******************************Output*******************************//I'm happy.//*******************************************************************
LINQ中的扩展方法
LINQ为IEnumerable<T>接口提供给了各种扩展方法,以便用户在实现了该接口的任意集合上使用LINQ查询。本节主要研究LINQ中Where扩展方法的实现,这个扩展方法位于System.Linq命名空间下的Enumerable类中。
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {if (source == null) throw Error.ArgumentNull("source");if (predicate == null) throw Error.ArgumentNull("predicate");if (source is Iterator<TSource>) return ((Iterator<TSource>)source).Where(predicate);if (source is TSource[]) return new WhereArrayIterator<TSource>((TSource[])source, predicate);if (source is List<TSource>) return new WhereListIterator<TSource>((List<TSource>)source, predicate);return new WhereEnumerableIterator<TSource>(source, predicate);}
由上述代码可以看出,Where方法是对IEnumberable接口的扩展,需要传入一个委托参数predicate,该委托要求返回布尔类型。假设我们对List<T>类型的对象调用Where方法,则返回一个WhereListIterator<TSource>对象。WhereListIterator<TSource>类派生自Iterator<TSource>类,下面是Iterator<TSource>类的源码,这里我们只需要注意GetEnumerator方法,该方法对于同一个线程,返回同一个迭代器,不同线程则克隆一个,并将state属性设置为1。
abstract class Iterator<TSource> : IEnumerable<TSource>, IEnumerator<TSource>{int threadId;internal int state;internal TSource current;public Iterator() {threadId = Thread.CurrentThread.ManagedThreadId;}public TSource Current {get { return current; }}public abstract Iterator<TSource> Clone();public virtual void Dispose() {current = default(TSource);state = -1;}public IEnumerator<TSource> GetEnumerator() {if (threadId == Thread.CurrentThread.ManagedThreadId && state == 0) {state = 1;return this;}Iterator<TSource> duplicate = Clone();duplicate.state = 1;return duplicate;}public abstract bool MoveNext();public abstract IEnumerable<TResult> Select<TResult>(Func<TSource, TResult> selector);public abstract IEnumerable<TSource> Where(Func<TSource, bool> predicate);object IEnumerator.Current {get { return Current; }}IEnumerator IEnumerable.GetEnumerator() {return GetEnumerator();}void IEnumerator.Reset() {throw new NotImplementedException();}}
此时,再回到WhereListIterator<TSource>类,该类重写了MoveNext方法。首先,调用GetEnumerator方法获得一个枚举器,在While循环中,只要MoveNext方法返回true,就用Current属性获得集合当前的元素,并使用委托predicate引用的方法处理该元素,返回剩余元素中满足条件的第一个元素。当遍历结束,调用Dispose方法释放非托管资源,并将state属性设置为-1。
class WhereListIterator<TSource> : Iterator<TSource>{List<TSource> source;Func<TSource, bool> predicate;List<TSource>.Enumerator enumerator;public WhereListIterator(List<TSource> source, Func<TSource, bool> predicate) {this.source = source;this.predicate = predicate;}public override Iterator<TSource> Clone() {return new WhereListIterator<TSource>(source, predicate);}public override bool MoveNext() {switch (state) {case 1:enumerator = source.GetEnumerator();state = 2;goto case 2;case 2:while (enumerator.MoveNext()) {TSource item = enumerator.Current;if (predicate(item)) {current = item;return true;}}Dispose();break;}return false;}public override IEnumerable<TResult> Select<TResult>(Func<TSource, TResult> selector) {return new WhereSelectListIterator<TSource, TResult>(source, predicate, selector);}public override IEnumerable<TSource> Where(Func<TSource, bool> predicate) {return new WhereListIterator<TSource>(source, CombinePredicates(this.predicate, predicate));}}
源码传送门:http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,dc4c4c53ff606bc0
延迟加载
延迟执行
在运行期间定义查询表达式时,查询不会运行,只有在迭代时才进行计算。
下面的示例定义了一个LINQ查询,从集合中找出姓名以A开头的所有员工,因为迭代在查询定义时不会进行,而是在执行每个foreach语句时进行。
var nameStartWithA = from e in employeeswhere e.EmployeeName.StartsWith("A")select e;Console.WriteLine("First iteration : ");foreach (var item in nameStartWithA){Console.WriteLine(item.EmployeeName);}Console.WriteLine();employees.Add(new Employee("011", "Lily", 25, new DateTime(2017, 5, 29), Sex.Female, Department.HR, 100000, new string[] { "shopping" }));employees.Add(new Employee("012", "Leo", 28, new DateTime(2017, 5, 29), Sex.Male, Department.IT, 200000, new string[] { "reading" }));employees.Add(new Employee("013", "Amelia", 29, new DateTime(2017, 5, 29), Sex.Female, Department.PD, 200000, new string[] { "reading", "run" }));employees.Add(new Employee("014", "Ava", 32, new DateTime(2017, 5, 29), Sex.Female, Department.PD, 400000, new string[] { "swimming" }));Console.WriteLine("Second iteration : ");foreach (var item in nameStartWithA){Console.WriteLine(item.EmployeeName);}//******************************Output*******************************//First iteration ://Adolph//Antony//Asa//Aimee//Second iteration ://Adolph//Antony//Asa//Aimee//Amelia//Ava//*******************************************************************
补充:延迟加载的工作原理可从上一章节中对源码的分析得出。
立即执行
查询在定义表达式时立即执行,而不是在迭代中进行。通过调用ToArray()、ToList()等扩展方法可以实现此项操作。
下面,我们修改上一节中的示例来说明:
var nameStartWithA = (from e in employeeswhere e.EmployeeName.StartsWith("A")select e).ToList();Console.WriteLine("First iteration : ");foreach (var item in nameStartWithA){Console.WriteLine(item.EmployeeName);}Console.WriteLine();employees.Add(new Employee("011", "Lily", 25, new DateTime(2017, 5, 29), Sex.Female, Department.HR, 100000, new string[] { "shopping" }));employees.Add(new Employee("012", "Leo", 28, new DateTime(2017, 5, 29), Sex.Male, Department.IT, 200000, new string[] { "reading" }));employees.Add(new Employee("013", "Amelia", 29, new DateTime(2017, 5, 29), Sex.Female, Department.PD, 200000, new string[] { "reading", "run" }));employees.Add(new Employee("014", "Ava", 32, new DateTime(2017, 5, 29), Sex.Female, Department.PD, 400000, new string[] { "swimming" }));Console.WriteLine("Second iteration : ");foreach (var item in nameStartWithA){Console.WriteLine(item.EmployeeName);}//******************************Output*******************************//First iteration ://Adolph//Antony//Asa//Aimee//Second iteration ://Adolph//Antony//Asa//Aimee//*******************************************************************
从输出结果中可以看出,两次迭代输出的结果相同,但是集合中值改变了。
示例代码下载:https://github.com/Answer-Geng/LINQ
转载于:https://www.cnblogs.com/zhangyingai/p/7074508.html
从LINQ开始之LINQ to Objects(下)相关推荐
- Pro LINQ 之三:LINQ to DataSet
写在前面 将LINQ to DataSet单独放一篇,是因为随后的LINQ to SQL默认只支持MS SQL Server.只有LINQ to DataSet能在没有相应Data Provider帮 ...
- LINQ编程之LINQ to SQL
最近比较忙,博客更新的比较慢,请大家谅解! 今天来简单介绍一下LINQ to SQL的知识.(用实例) 什么是LINQ to SQL? LINQ to SQL是基于关系数据的.NET语言集成查询,用于 ...
- Linq初级班 Linq To XML体验(基础篇)
LINQ To XML体验(基础) 这两天开始学习LINQ to XML的知识,我会继续把自己的感想和示例发布给初学者们学习的,一样欢迎高手们多多指点,请勿使用过激语言,针锋相对,我是个初学者,自知还 ...
- LINQ圣经——《LINQ实战》
媒体评论 本书作者是 LINQ 社区中的重要成员,他们在书中展示了这一技术的精髓--快去享受这一盛宴吧. --Matt Warren ,微软主架构师, LINQ 之父 LINQ 方面的杰作!深入.全面 ...
- LINQ系列:LINQ to SQL Exists/In/Any/All/Contains
1. Any 返回没有Product的Category var expr = from c in context.Categorieswhere !c.Products.Any()select c; ...
- LINQ体验(6)——LINQ to SQL语句之Join和Order By
LINQ体验(6)--LINQ to SQL语句之Join和Order By Join操作 适用场景:在我们表关系中有一对一关系,一对多关系,多对多关系等.对各个表之间的关系,就用这些实现对多个表的操 ...
- C# LINQ系列:LINQ to DataSet的DataTable操作 及 DataTable与Linq相互转换
LINQ to DataSet需要使用System.Core.dll.System.Data.dll和System.Data.DataSetExtensions.dll,在项目中添加引用System. ...
- 光脚丫学LINQ(014):LINQ to SQL简介
视频演示:http://u.115.com/file/f29f7838f6 LINQ to SQL 是 .NET Framework 3.5 版的一个组件,提供了用于将关系数据作为对象管理的运行时基础 ...
- 【LINQ语句】LINQ语句
前言 LINQ(Language Integrated Query)语言集成查询是一组用于c#和Visual Basic语言的扩展.它允许编写C#或者Visual Basic代码以查询数据库相同的方式 ...
最新文章
- 启信宝CEO陈青山:AI+大数据驱动下的金融新生态
- 万字干货 | Python后台开发的高并发场景优化解决方案
- bpmn js 生成json_你不知道的 tsconfig.json
- 在各路由器进行OSPF的基本配置
- poj-1064Cable master(二分)
- 进程线程协程-基本概念及特点
- linux5 syscall 流程_Linux的上的程序是如何运行的,api机制是怎么样?
- 用户界面设计参考 (ZT)
- C++并发与多线程(一)线程传参
- linux 网络编程connection refused,《UNIX网络编程》第一个例子出现“connect error: Connection refused”错误提示信息?...
- 生信过程中的各种文件格式
- 敌兵布阵 HDU - 1166(树状数组-模板线段树写法)
- log 1用计算机怎么打开,log是什么?log怎么打开?
- 前段时间的世界互联网大会
- AI芯片:寒武纪DianNao,英伟达NVDLA和谷歌TPU1的芯片运算架构对比分析
- php微信自动回复开发,PHP微信开发之文本自动回复
- ubuntu安装CD-HIT
- HanLP极致简繁转换详细讲解 1
- JS给指定元素添加父元素
- GO_strings.Reader
热门文章
- DevExpress学习之DevExpress.XtraGrid.Columns.GridColumn
- CSS Sprites图片拼合生成器实现思路
- 去了新公司,物理通过
- Deep Learning回顾之LeNet、AlexNet、GoogLeNet、VGG、ResNet
- 运用深度学习教机器人理解自然语言
- Facebook发布人工智能产品DeepText:能以人类智商
- 海尔智慧家庭食联网:为每1个家庭配备专属健康顾问
- hdu 1016 Prime Ring Problem(DFS)
- 【译】为什么这样宏定义#define INT_MIN (-2147483647 - 1)?
- 如何激励用户为你的app评分?