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

  • 相关阅读:
    分库分表(1) --- 理论
    Elasticsearch(10) --- 内置分词器、中文分词器
    Elasticsearch(9) --- 聚合查询(Bucket聚合)
    Elasticsearch(8) --- 聚合查询(Metric聚合)
    Elasticsearch(7) --- 复合查询
    Elasticsearch(6) --- Query查询和Filter查询
    Elasticsearch(5) --- 基本命令(集群相关命令、索引CRUD命令、文档CRUD命令)
    第二周 Word版面设计
    第六周 Word目录和索引
    第五周 Word注释与交叉引用
  • 原文地址:https://www.cnblogs.com/Answer-Geng/p/6905630.html
Copyright © 2020-2023  润新知