前言


上一篇《从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 employees
where 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 employees
where 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

从LINQ开始之LINQ to Objects(下)的更多相关文章

  1. LINQ简介和LINQ to SQL语句之Where

    LINQ是Language Integrated Query的简称,它是集成在.NET编程语言中的一种特性.已成为编程语言的一个组成部分,在编写程序时可以得到很好的编译时语法检查,丰富的元数据,智能感 ...

  2. LINQ系列:Linq to Object量词操作符

    量词操作符返回一个Boolean值,指示序列中是否存在部分或全部元素符号指定条件.LINQ中量词操作符包括:All.Any和Contains. 1. All All操作符判定在集合中是否所有的值都满足 ...

  3. LINQ系列:LINQ to SQL Exists/In/Any/All/Contains

    1. Any 返回没有Product的Category var expr = from c in context.Categories where !c.Products.Any() select c ...

  4. LINQ / LINQ to SQL / LINQ to XXX 它们到底有什么区别

    LINQ是新生事物,不过从不少文章和讨论上看来,这方面的概念也已经有点混沌不清了.因此我们经常可以看到这样的话: LINQ只能将数据表与实体属性一一对应…… LINQ开发指南:在LINQ中进行数据库字 ...

  5. LINQ to XML LINQ学习第一篇

    LINQ to XML LINQ学习第一篇 1.LINQ to XML类 以下的代码演示了如何使用LINQ to XML来快速创建一个xml: public static void CreateDoc ...

  6. C# LINQ系列:LINQ to DataSet的DataTable操作 及 DataTable与Linq相互转换

    LINQ to DataSet需要使用System.Core.dll.System.Data.dll和System.Data.DataSetExtensions.dll,在项目中添加引用System. ...

  7. Linq实战 之 Linq to Sql及Entity Framework操作详解

    Linq实战 之 Linq to Sql及Entity Framework操作详解 一:linq to db的框架 1. linq to sql 2. linq to ado.net entity f ...

  8. LINQ体验(13)——LINQ to SQL语句之运算符转换和ADO.NET与LINQ to SQL

    运算符转换 1.AsEnumerable:将类型转换为泛型 IEnumerable 使用 AsEnumerable<TSource> 可返回类型化为泛型 IEnumerable 的參数.在 ...

  9. [LINQ2Dapper]最完整Dapper To Linq框架(四)---Linq和SQL并行使用

    目录 [LINQ2Dapper]最完整Dapper To Linq框架(一)---基础查询 [LINQ2Dapper]最完整Dapper To Linq框架(二)---动态化查询 [LINQ2Dapp ...

随机推荐

  1. Spring-quartz 可传参(包括service注入)任务调度 多个任务调度

    1 JobDetail 注意:一个JobDetail中 只能触发一个方法,如果要调度多个任务 需要有多个job类! 普通任务:总调度(SchedulerFactoryBean)--> 定时调度器 ...

  2. jQuery中append(),prepend()与after(),before()的区别

    在jQuery中,添加元素有append(),prepend和 after(),before()两种共四个. 根据字面意思我们可以看出他们分别是追加,添加和之前,之后,意思相近.同时他们又都有添加元素 ...

  3. Robot framework的介绍

    Robot framework是基于Python语言编写的功能自动化测试框架.使用简单,不懂编码的测试人员也能像编程一样写测试用例,支持关键字驱动测试并且可以开发系统关键字.还有丰富的第三方库,比如S ...

  4. 网络语音视频技术浅议(附多个demo源码下载)

    我们在开发实践中常常会涉及到网络语音视频技术.诸如即时通讯.视频会议.远程医疗.远程教育.网络监控等等,这些网络多媒体应用系统都离不开网络语音视频技术.本人才疏学浅,对于网络语音视频技术也仅仅是略知皮 ...

  5. 使用Block传值

    使用Block的地方很多,其中传值只是其中的一小部分,下面介绍Block在两个界面之间的传值: 先说一下思想: 首先,创建两个视图控制器,在第一个视图控制器中创建一个UILabel和一个UIButto ...

  6. 0-创建scott示例数据

    CREATE TABLE dept (  deptno INT PRIMARY KEY,  dname VARCHAR(14),  loc VARCHAR(13) );   INSERT INTO d ...

  7. 大数据及hadoop相关知识介绍

    一.大数据的基本概念 1.1什么是大数据 互联网企业是最早收集大数据的行业,最典型的代表就是Google和百度,这两个公司是做搜索引擎的,数量都非常庞大,每天都要去把互联网上的各种各样的网页信息抓取下 ...

  8. IntelliJ-项目配置,解决no artifacts的warnings

    1.名词解释 artifacts:是maven中的一个概念,表示项目/modules如何打包,比如jar,war,war exploded,ear等打包形式, 一个项目或者说module有了artif ...

  9. 转 JSON与XML转换

    这两天处理模块的联调工作,在json与XML转换中出现了一些奇怪的问题,仔细究来,实为对org.json.*包知之太少.晚上baidu.google一下,找出了问题出现的原因.在模块中,使用了两个方法 ...

  10. Oracle数据库时间类型悬疑案

    这次遇到的问题小Alan其实一年半前做证券行业项目就已经遇到过,但是一直没有去思考是什么原因导致的这样的悬疑案,悬疑案是什么呢?其实很简单,我想有不少童鞋都有用到Oracle数据库,情形是这样子的,这 ...