从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 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

时间: 2024-10-14 08:37:29

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

Linq学习(二)-LinQ to Entity

在昨天我学习了LinQ的一些基础知识和动手写了一些LinQ to Object的例子的基础上,对于LinQ语法和基本的要点有了一定的了解.今天继续自己的学习,对于今天学习的LinQ to DataSet 和LinQ to Entity做自己的一些总结,一方面加深自己的理解,另一方面也能掌握LinQ技术的实现机制,对于也跟我一样对着一方面有兴趣的也可以让大家有个初步的感性认识,也是好的. 今天主要的篇幅会讲解LinQ to Entity的C#实现机制以及解决昨天我看完一小节之后的两点疑惑,后面会花

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 framework linq to sql是一个团队 ef 是一个团队... linq to sql => ef 团队. linq to sql 适合一些小型的项目 => sqlserver ef 适合中形的项目,而且可以支持 sqllite,mysql,sqlserver 掌柜的项目开发中:使用的

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; SELECT [Extent1].[CategoryID] AS [CategoryID], [Extent1].[CategoryName] AS [CategoryName] FROM [dbo].[Category] AS [Extent1] WHERE NOT EXISTS (SELEC

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

运算符转换 1.AsEnumerable:将类型转换为泛型 IEnumerable 使用 AsEnumerable<TSource> 可返回类型化为泛型 IEnumerable 的參数.在此演示样例中,LINQ to SQL(使用默认泛型 Query)会尝试将查询转换为 SQL 并在server上运行. 但 where 子句引用用户定义的client方法 (isValidProduct),此方法无法转换为 SQL. 解决方法是指定 where 的client泛型 IEnumerable<

从LINQ开始之LINQ to Objects(上)

LINQ概述 LINQ,语言集成查询(Language Integrated Query),它允许使用C#或VB代码以查询数据库相同的方式来操作不同的数据源. LINQ体系结构 从上图可以看出,LINQ总共包括五个部分:LINQ to Objects.LINQ to DataSets.LINQ to SQL.LINQ to Entities.LINQ to XML.LINQ to Objects:对内存中集合的操作LINQ to DataSets:对数据集Datatable的操作LINQ to

LINQ体验(11)——LINQ to SQL语句之Null语义和String/DateTime方法

在本系列中.主要介绍LINQ to SQL基础的东西,由于LINQ太强大了,它对我们寻常使用不同的数据源有着不同的内容,其包含对于SQL Server 数据库的LINQ to SQL:对于XML 文档的LINQ to XML.对于 ADO.NET 数据集的LINQ to DataSet.对于.NET 集合.文件.字符串等的LINQ to Objects.例外也出现了一些对LINQ支持的开源项目,比如LINQ to JSON,LINQ for NHibernate等等. 在这个系列中,一些关于LI

LINQ体验(7)——LINQ to SQL语句之Group By/Having和Exists/In/Any/All/Contains

我们继续讲解LINQ to SQL语句,这篇我们来讨论Group By/Having操作符和Exists/In/Any/All/Contains操作符. Group By/Having操作符 适用场景:分组数据,为我们查找数据缩小范围. 说明:分配并返回对传入参数进行分组操作后的可枚举对象.分组:延迟 1.简单形式: var q = from p in db.Products group p by p.CategoryID into g select g; 语句描述:使用Group By按Cat

LINQ体验(18)——LINQ to SQL语句之视图和继承支持

视图 我们使用视图和使用数据表类似,只需将视图从"服务器资源管理器/数据库资源管理器"拖动到O/R 设计器上,自动可以创建基于这些视图的实体类.我们可以同操作数据表一样来操作视图了.这里注意:O/R 设计器是一个简单的对象关系映射器,因为它仅支持 1:1 映射关系.换句话说,实体类与数据库表或视图之间只能具有 1:1 映射关系.不支持复杂映射(例如,将一个实体类映射到多个表).但是,可以将一个实体类映射到一个联接多个相关表的视图. 下面使用NORTHWND数据库中自带的Invoices

LINQ体验(17)——LINQ to SQL语句之动态查询

高级特性 本文介绍LINQ的高级特性,其包括大家都关心的动态查询的用法,另外简单提下ID标识这个知识. 动态查询 有这样一个场景:应用程序可能会提供一个用户界面,用户可以使用该用户界面指定一个或多个谓词来筛选数据.这种情况在编译时不知道查询的细节,动态查询将十分有用. 在LINQ中,Lambda表达式是许多标准查询运算符的基础,编译器创建lambda表达式以捕获基础查询方法(例如 Where.Select.Order By.Take While 以及其他方法)中定义的计算.表达式目录树用于针对数