从LINQ开始之LINQ to Objects(下)

前言


上一篇《从LINQ开始之LINQ to Objects(上)》主要介绍了LINQ的体系结构、基本语法以及LINQ to Objects中标准查询操作符的使用方法。

本篇则主要讨论LINQ to Objects中的扩展方法以及延迟加载等方面的内容。


扩展方法


1.扩展方法简介

  扩展方法能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或其他方式修改原始类型。扩展方法是静态方法,它是类的一部分,但实际没有放在类的源代码当中。

下面,我们来看一个简单示例,为上一篇中定义的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方法。

2.使用扩展方法来扩展接口

  把方法扩展到某个接口中,实现该接口的多个类就可以使用相同的实现代码。

以下示例介绍了扩展方法扩展接口的使用场景,首先,定义了一个接口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.//*******************************************************************

3.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.Cur
                 rent;                      
                   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


延迟加载


1.延迟执行

  在运行期间定义查询表达式时,查询不会运行,只有在迭代时才进行计算。

下面的示例定义了一个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//*******************************************************************

补充:延迟加载的工作原理可从上一章节中对源码的分析得出。

2.立即执行

  查询在定义表达式时立即执行,而不是在迭代中进行。通过调用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

原文地址:http://www.cnblogs.com/Answer-Geng/p/6905630.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/325370.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Localdatetime

根据指定日期/时间创建对象 LocalDate localDate LocalDate.of(2018, 1, 13); LocalTime localTime LocalTime.of(9, 43, 20); LocalDateTime localDateTime LocalDateTime.of(2018, 1, 13, 9, 43, 20); System.out.println(localDate); System.out.println(localTime); Sy…

基于OAuth2的认证(译)

OAuth 2.0 规范定义了一个授权&#xff08;delegation&#xff09;协议&#xff0c;对于使用Web的应用程序和API在网络上传递授权决策非常有用。OAuth被用在各钟各样的应用程序中&#xff0c;包括提供用户认证的机制。这导致许多的开发者和API提供者得出一个OAuth本身是一个认证…

Redis非阻塞I/O多路复用机制

小曲在S城开了一家快递店&#xff0c;负责同城快送服务。小曲因为资金限制&#xff0c;雇佣了一批快递员&#xff0c;然后小曲发现资金不够了&#xff0c;只够买一辆车送快递。 经营方式一 客户每送来一份快递&#xff0c;小曲就让一个快递员盯着&#xff0c;然后快递员开车去…

React前端格式化时间

import moment from "moment";const dateFormat YYYY-MM-DD HH:mm:ss;<DatePicker label"时间" name"insertTime" showTime onChange{onChange} onOk{onOk}defaultValue{moment(location?.defaultValues?.record?.insertTime, dateFormat…

[认证授权] 4.OIDC(OpenId Connect)身份认证授权(核心部分)

1 什么是OIDC&#xff1f; 看一下官方的介绍&#xff08;http://openid.net/connect/&#xff09;&#xff1a; OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol. It allows Clients to verify the identity of the End-User based on the a…

EasyExcel中输出为时间格式

前端传值 后端Excel配置为String类型 配置为其他类型显示格式转化异常

Identity Service - 解析微软微服务架构eShopOnContainers(二)

接上一篇&#xff0c;众所周知一个网站的用户登录是非常重要&#xff0c;一站式的登录&#xff08;SSO&#xff09;也成了大家讨论的热点。微软在这个Demo中&#xff0c;把登录单独拉了出来&#xff0c;形成了一个Service&#xff0c;用户的注册、登录、找回密码等都在其中进行…

TCP/IP协议——ARP详解

转载自 TCP/IP协议——ARP详解 本文主要讲述了ARP的作用、ARP分组格式、ARP高速缓存、免费ARP和代理ARP。 1.学习ARP前要了解的内容 建立TCP连接与ARP的关系 应用接受用户提交的数据&#xff0c;触发TCP建立连接&#xff0c;TCP的第一个SYN报文通过connect函数到达IP层&a…

RPC远程过程调用之 RMI实现

1&#xff09;RMI&#xff08;remote method invocation&#xff09;是java原生支持的远程调用&#xff0c;RMI采用JRMP&#xff08;java RemoteMessageing Protocol&#xff09;作为通信协议。可以认为是纯java版本的分布式远程调用解决方法。 2&#xff09;RMI的核心概念 3&…

[翻译]编写高性能 .NET 代码 第一章:性能测试与工具 -- 选择什么来衡量

选择什么来衡量 在搜集数据测试数据前&#xff0c;你需要知道你要以怎样的指标来衡量测试结果。这听起来很容易&#xff0c;但实际上比你想象中的要难许多。如果你想降低内存使用量&#xff0c;你会选择什么方式呢&#xff1f; 私有工作集&#xff08;Private working set&am…

RPC远程过程调用之Hessian 基于HTTP

Hessian使用C/S方式&#xff0c;基于HTTP协议传输&#xff0c;使用Hessian二进制序列化。 添加依赖&#xff1a; <dependency><groupId>com.caucho</groupId><artifactId>hessian</artifactId><version>4.0.7</version> </depen…

EF框架中,在实体中手动更新字段,数据库数据未同步到程序中应该怎么解决呢?

在一些技术不是很强的选手手中&#xff0c;设计数据库时&#xff0c;难免会未考虑到某些字段&#xff0c;只能到后期实现功能时&#xff0c;才能觉察出来数据库中或是少写字段&#xff0c;或是多加了无用的字段&#xff0c;故我们还不得不去数据库中做些手脚。 本文列举的是在…

[.NET跨平台]Jexus独立版本的便利与过程中的一些坑

本文环境与前言 之前写过一篇相关的文章:在.NET Core之前,实现.Net跨平台之MonoCentOSJexus初体验 当时的部署还是比较繁琐的,而且需要联网下载各种东西..有兴趣的可以看看,但是..已经过时了.. 虽然已经出了.NET Core2.0 但是目前是预览版本,而且部署来说 相对比较麻烦. 今…

《四世同堂》金句摘抄(二)

System.out.println("今天读的是什么书呢&#xff1f;"); String bname "《四世同堂》"; System.out.println("今天读的书是&#xff1a;"bname);Console.WriteLine("今天读的是什么书呢&#xff1f;"); String bname "《四世同…

码农不重视文档:开源项目深受其苦

GitHub 刚刚发布了《2017 开源调查》报告&#xff0c;这家知名编程社交网站走访了 github.com 社区内 3800 多个项目和超过 5500 名成员&#xff0c;并且与 500 名在 GitHub 项目生态系统之外的编程者们进行了交谈。本次调查涉及多方面&#xff0c;比如人们在为开源项目做开发、…

[翻译]编写高性能 .NET 代码 第一章:性能测试与工具 -- 平均值 vs 百分比

平均值 vs 百分比 在考虑要性能测试的目标值时&#xff0c;我们需要考虑用什么统计口径。大多数人都会首选平均值&#xff0c;但在大多数情况下&#xff0c;这个正确的&#xff0c;但你也应该适当的考虑百分数。但你有可用性的要求&#xff0c;作为性能测试的目标里肯定会有用…

面对枯燥的源码,如何才能看得下去

转载自 面对枯燥的源码&#xff0c;如何才能看得下去 一个软件开发人员&#xff0c;工作到了一定的年限&#xff08;一般是3、4年左右&#xff09;&#xff0c;如果他还没学会阅读源码&#xff0c;那么他就会遇到瓶颈。因为到了这个时候的开发&#xff0c;他应该不仅仅只会做…

前端防xss攻击(去掉空格等能影响和攻击数据库的字段)

用法&#xff1a;把要传给后端的数据放到此能过滤的函数里面即可就能实现自动过滤的功能 eg 代码 export function XSSFilter(str: string) {// 过滤emoji表情str str.replace(/\uD83C[\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F]/g, );// 过滤所有html标签str str.replace(/&l…

[深圳/广州]微软SQL技术沙龙分享会(MVP)

[深圳/广州] 新一期俱乐部活动报名开始&#xff0c;这次是广深地区SQL Server 技术沙龙分享会&#xff08;MVP)&#xff0c;SQL Server作为一个数据平台&#xff0c;不管是SQL Server 2017 on Linux 还是把一门开源R语言融入其最赚钱的旗舰商业数据库&#xff0c;微软已经决定&…