关于.NET中迭代器的实现以及集合扩展方法的理解

在C#中所有的数据结构类型都实现IEnumerable或IEnumerable<T>接口(实现迭代器模式),可以实现对集合遍历(集合元素顺序访问)。换句话可以这么说,C#提供的只要实现上面这两个接口的类都是集合类,都能够进行遍历。工作中用过很多扩展方法对泛型集合(IEnumerable<T>)元素进行处理,一直很想一探究竟,通过参考一些资料(主要是大话设计模式和C#从现象到本质这两本书),总结下自己对迭代器和扩展方法的一些理解:

 

一、自己实现一个迭代器。.NET平台已经提供了IEnumerable和IEumerator及泛型版本接口,不需要再建了,实现他们就好。

  1. 建一个集合类要实现IEnumerable<T>接口,实现接口方法GetEnumerator()返回一种迭代器对象,并将集合关联(引用)到迭代器中,这里集合类不直接使用List<T>,因为它已经实现了迭代器,而是自己实现一个能够迭代的集合类。代码:
       /// <summary>
        /// 泛型集合类
        /// </summary>
        public class MyAggregate<T> : IEnumerable<T>
        {
            private IList<T> list = new List<T>();
            public int Count
            {
                get { return list.Count; }
            }
            public T this[int count]
            {
                get { return list[count]; }
                set { list.Insert(count, value); }
            }
            public void Add(T item)
            {
                list.Add(item);
            }
            public void Remove(T item)
            {
                list.Remove(item);
            }
            public IEnumerator<T> GetEnumerator()
            {
                return new MyEnumerator<T>(this,0); //MyEnumerator<T>为升序迭代器
            }
                IEnumerator IEnumerable.GetEnumerator() => new MyEnumerator<T>(this,0);
        }
  2. 建迭代器类(升序、倒序)要实现IEnumerator<T>接口,这里在MoveNext()方法中加入了状态管理(switch case代码部分)。代码:
        /// <summary>
        /// 升序迭代器
        /// </summary>
        public class MyEnumerator<T> : IEnumerator<T>
        {
            private MyAggregate<T> _aggregate;//遍历的集合
            private T _current;//当前集合元素,越界停留最后一个元素
            private int _position = -1;//当前位置
            private int _state;//当前状态,标识迭代时MoveNext()从哪里恢复执行
            public MyEnumerator(MyAggregate<T> aggregate,int state)
            {
                this._aggregate = aggregate;
                this._state = state;
            }
            //只读
            public T Current => this._current;
            object IEnumerator.Current => this._current;
            //无越界返回true并将集合元素赋值_current,越界返回false
            public bool MoveNext()
            {
                switch (this._state)
                {
                    case 0://调用未执行状态
                        this._state = -1;//运行状态
                        this._position = 0;
                        break;
                    case 1://标识下次恢复执行状态
                        this._state = -1;
                        this._position++;
                        break;
                    default://-1越界
                        return false;
                }
                bool result;
                if (this._position < this._aggregate.Count)
                {
                    this._current = this._aggregate[_position];
                    this._state = 1;
                    result = true;
                    return result;
                }
                result = false;
                return result;
            }
            public void Reset()
            {
                this._state = 0;//恢复迭代器调用未执行状态(MoveNext)
            }
            public void Dispose() { }
    }

  这时候,迭代器已经实现了,MyAggregate<T>对象集合便有了迭代功能,可以实现对集合元素的顺序访问(遍历)。建一个Person类,测试一下。代码:

   public class Person
    {
        public Person(string name,int age) {
            this.Name = name;
            this.Age = age;
        }
        public string Name { get; set; }
        public int Age { get; set; }

        public override string ToString()
        {
            return string.Format("我叫{0},今年{1}岁", this.Name, this.Age);
        }
   }

  主函数代码:

     static void Main(string[] args)
        {
            MyAggregate<Person> aggregate = new MyAggregate<Person>();//处理有原始容器集合,元素会改变
            aggregate[0] = new Person("张三", 15);
            aggregate[1] = new Person("李四", 16);
            aggregate.Add(new Person("王二", 17));
            aggregate.Add(new Person("麻子", 18));

            //集合类实现IEnumerable<T> 接口,将集合类交给具体迭代器 迭代处理
            var iterator = aggregate.GetEnumerator();
            while (iterator.MoveNext())
            {
                Console.WriteLine(iterator.Current);
            }
            foreach (var item in aggregate)
            {
                item.Age = 20;//item即aggregate.Current只读,迭代元素值不能更改,可以改元素属性
                Console.WriteLine(item);
            }
            foreach (var item in aggregate)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
        }

  运行结果:

关于.NET中迭代器的实现以及集合扩展方法的理解

这里

foreach (var item in aggregate)
{
    Console.WriteLine(item);
}

由编译器生成IL,本质其实就是下面这几行代码,两个作用一样,只是foreach语句可读性更强。

//foreach语句本质
var iterator = aggregate.GetEnumerator();
while (iterator.MoveNext())
{
    Console.WriteLine(iterator.Current);
}

  原始迭代器实现方式比较麻烦,代码要多建一个附加类(上面的MyEnumerator<T>类),可以用提供的yield return 关键字进行简化,将上面集合类中的GetEnumerator()方法改写成:

public IEnumerator<T> GetEnumerator()
{
    for (int count = 0; count < this.Count; count++)
    {
        yield return this[count];
    }
}

  这样不需要再创建附加类,就可以达到上面代码一样的效果,能够用关键字简化编译器自动生成等效的IL代码,原因是实现IEnumerable<T>接口方法都是一样的。

注:yield return只能用在返回值为IEnumerable和IEnumerator类型的方法中,会将MoveNext()变成状态判断的状态机,所谓的延迟加载(取值才真正运行有yield的方法)其实本质就是调用GetEnumerator方法只是获取一个迭代器对象(将集合数据关联到该对象中处理,迭代遍历取值完全交给迭代器对象进行处理),不取值是不会运行IEnumerator(迭代器对象)中MoveNext方法的,状态一直为0,所以可以通过后面加ToList()等方法遍历取值才能真正运行该方法。

 

二、自己实现简易的扩展方法静态类,实现类似的功能。工作中总是避免不了用到一些扩展方法(linq的基础,都是通过扩展方法实现的)处理泛型集合,常用的有Where()、Select()、SelectMany()、FirstOrDefault()、LastOrDefault()、OrderBy()、OrderByDescending()、GroupBy()等等,感觉功能非常强大使用方便,于是根据用法简单模仿实现一下。代码:

public static class MyEnumerable
{
    //public static IEnumerable<TSource> MyWhere<TSource(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    //{
    //    MyAggregate<TSource> aggregate = new MyAggregate<TSource>();
    //    foreach (TSource ts in source)
    //    {
    //        if (predicate(ts))
    //        {
    //            aggregate.Add(ts);
    //        }
    //    }
    //    return aggregate;
    //}
    //延迟执行,yield return 用在返回值为IEnumerable和IEnumerator中
    public static IEnumerable<TSource> MyWhere<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    {
        foreach (TSource ts in source)
        {
            if (predicate(ts))
            {
                yield return ts;
            }
        }
    }

    //public static IEnumerable<TResult> MySelect<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
    //{
    //    MyAggregate<TResult> aggregate = new MyAggregate<TResult>();
    //    foreach (TSource ts in source)
    //    {
    //        aggregate.Add(selector(ts));
    //    }
    //    return aggregate;
    //}
    public static IEnumerable<TResult> MySelect<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
    {
        foreach (TSource ts in source)
        {
            yield return selector(ts);
        }
    }
}

主函数代码:

        static void Main(string[] args)
        {
            MyAggregate<Person> aggregate = new MyAggregate<Person>();//处理有原始容器集合,元素会改变
            aggregate[0] = new Person("张三", 15);
            aggregate[1] = new Person("李四", 16);
            aggregate.Add(new Person("王二", 17));
            aggregate.Add(new Person("麻子", 18));

            IEnumerable<Person> people = aggregate.MyWhere(p => p.Age > 16).ToList(); 
            foreach (var person in people)
            {
                Console.WriteLine(person);
            }

            IEnumerable<string> strs = aggregate.MySelect(p => { if (p.Age < 18) return "我叫" + p.Name + ",未成年"; else return "我叫" + p.Name + ",已成年"; });
           
            IEnumerable<string> strs1 = aggregate.MyWhere(p => p.Age > 16).MySelect(m => { if (m.Age < 18) return "我叫" + m.Name + ",未成年"; else return "我叫" + m.Name + ",已成年"; });
            foreach (var str in strs)
            {
                Console.WriteLine(str);
            }
            foreach (var str in strs1)
            {
                Console.WriteLine(str);
            }
            Console.ReadKey();
    }   

运行结果:

关于.NET中迭代器的实现以及集合扩展方法的理解

 

 

PS:第一次开始写博客,希望以后坚持下去,将博客作为笔记,记录工作或学习中自己的一些的总结和理解,能够看到自己一点一点的成长下去,加油!

上一篇:Iterator模式C++实现


下一篇:mysql count(*)、limit分页慢的终极解决方案新鲜出炉