迭代器

引言:

   在C# 1.0中我们经常使用foreach来遍历一个集合中的元素,然而一个类型要能够使用foreach关键字来对其进行遍历必须实现IEnumerableIEnumerable<T>接口,(之所以必须要实现IEnumerable这个接口,是因为foreach是迭代语句,要使用foreach必须要有一个迭代器才行的,然而IEnumerable接口中就有IEnumerator GetEnumerator()方法是返回迭代器的,所以实现了IEnumerable接口,就必须实现GetEnumerator()这个方法来返回迭代器,有了迭代器就自然就可以使用foreach语句了), 而在C# 1.0中要获得迭代器就必须实现IEnumerable接口中的GetEnumerator()方法,然而要实现一个迭代器就必须实现IEnumerator接口中的bool MoveNext()和void Reset()方法,以及Current属性,然而 C# 2.0中提供 yield关键字来简化迭代器的实现,这样在C# 2.0中如果我们要自定义一个迭代器就容易多了。下面就具体介绍了C# 2.0 中如何提供对迭代器的支持.

一、迭代器的介绍

   迭代器大家可以想象成数据库的游标,即一个集合中的某个位置,C# 1.0中使用foreach语句实现了访问迭代器的内置支持,使用foreach使我们遍历集合更加容易(比使用for语句更加方便,并且也更加容易理解),foreach被编译后会调用GetEnumerator来返回一个迭代器,也就是一个集合中的初始位置(foreach其实也相当于是一个语法糖,把复杂的生成代码工作交给编译器去执行)。

二、C#1.0如何实现迭代器

   在C# 1.0 中实现一个迭代器必须实现IEnumerator接口,下面代码演示了传统方式来实现一个自定义的迭代器:

    class Program
    {
        static void Main(string[] args)
        {

            List<Person> personList = new List<Person>
            {
                new Person {Id = 1, Name = "wjire1"},
                new Person {Id = 2, Name = "wjire2"},
                new Person {Id = 3, Name = "wjire3"},
                new Person {Id = 4, Name = "wjire4"},
                new Person {Id = 5, Name = "wjire5"},
                new Person {Id = 6, Name = "wjire6"},
            };

            Persons persons = new Persons(personList);
            foreach (var person in persons)
            {
                Console.WriteLine(person);
            }
            Console.ReadKey();
        }
    }

    /// <summary>
    /// 人的集合
    /// </summary>
    public class Persons : IEnumerable
    {

        private readonly List<Person> _persons;
        public Persons(List<Person> persons)
        {
            _persons = persons;
        }
        public IEnumerator GetEnumerator()
        {
            return new MyIterator(this);
        }

        public Person this[int index] => _persons[index];

        public int Count => _persons.Count;
    }

    /// <summary>
    /// 要实现自己的迭代器, 必须实现 IEnumerator 接口,这样就必须实现 IEnumerator 接口中的MoveNext、Reset方法和Current属性
    /// </summary>
    public class MyIterator : IEnumerator
    {
        private readonly Persons _persons;
        private int _index;
        private Person _current;
        public MyIterator(Persons persons)
        {
            _persons = persons;
            _index = 0;
        }

        public bool MoveNext()
        {
            if (_index + 1 <= _persons.Count)
            {
                this._current = _persons[_index];
                _index++;
                return true;
            }
            return false;

        }

        public void Reset()
        {
            _index = 0;
        }

        public object Current => this._current;
    }

 

或者( 推荐 泛型接口 )

 

    public class Person
    {
        public int Id { get; set; }

        public Person(int id)
        {
            this.Id = id;
        }

        public override string ToString()
        {
            return this.Id.ToString();
        }
    }


    public class Persons : IEnumerable<Person>
    {
        public Person[] PersonArray { get; set; }

        public Person this[int index] => PersonArray[index];

        public int Count => PersonArray.Length;

        public IEnumerator<Person> GetEnumerator()
        {
            return new PersonEnumerator(this);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }


    public class PersonEnumerator : IEnumerator<Person>
    {
        private Persons persons { get; }

        private int index { get; set; }

        public PersonEnumerator(Persons ps)
        {
            this.persons = ps;
            this.index = 0;
        }

        public bool MoveNext()
        {
            if (index < persons.Count)
            {
                this.Current = persons[index];
                index++;
                return true;
            }
            return false;

        }

        public void Reset()
        {
            index = 0;
        }


        public Person Current { get; set; }

        object IEnumerator.Current => this.Current;

        public void Dispose()
        {

        }
    }



static void Main(string[] args)
{
Person[] persons ={
new Person(1),
new Person(2),
new Person(3),
new Person(4),
new Person(5),
new Person(6),
};
Persons ps = new Persons { PersonArray = persons };


foreach (var person in ps)
{
Console.WriteLine(person);
}


Console.ReadKey();
}






 

三、使用C#2.0的新特性简化迭代器的实现

 

    /// <summary>
    /// 人的集合
    /// </summary>
    public class Persons : IEnumerable
    {

        private readonly List<Person> _persons;
        public Persons(List<Person> persons)
        {
            _persons = persons;
        }
        public IEnumerator GetEnumerator()
        {
            for (int i = 0; i < _persons.Count; i++)
            {
                //只需要这一句话就OK了
                yield return _persons[i];
            }
        }

        public Person this[int index] => _persons[index];

        public int Count => _persons.Count;
    }

 

在上面代码中有一个yield return 语句,这个语句的作用就是告诉编译器GetEnumerator方法不是一个普通的方法,而是实现一个迭代器的方法,当编译器看到yield return语句时,编译器知道需要实现一个迭代器,所以编译器生成中间代码时为我们生成了一个IEnumerator接口的对象,大家可以通过Reflector工具进行查看,下面是通过Reflector工具得到一张截图:

 

 

从上面截图可以看出,yield return 语句其实是C#中提供的另一个语法糖,简化我们实现迭代器的源代码,把具体实现复杂迭代器的过程交给编译器帮我们去完成,看来C#编译器真是做得非常人性化,把复杂的工作留给自己做,让我们做一个简单的工作就好了。

 

迭代器工作流程

 

    class Program
    {
        private static readonly string Padding = new string(' ', 30);

        static void Main(string[] args)
        {
            IEnumerable<int> iEnumerable = CreateIEnumerable();//在第一次 调用 iEnumerator.MoveNext() 方法前,CreateIEnumerable() 方法里面的代码不会被执行
            IEnumerator<int> iEnumerator = iEnumerable.GetEnumerator();
            Console.WriteLine("Starting to iterable");
            while (true)
            {
                Console.WriteLine("Calling MoveNext()");
                bool result = iEnumerator.MoveNext();//所有工作在 iEnumerator.MoveNext() 调用时就完成了,后面的 iEnumerator.Current 不会执行任何代码
                Console.WriteLine(" Move result = {0}", result);
                if (!result)
                {
                    break;
                }
                Console.WriteLine("Fetching Current");
                Console.WriteLine($"Current result = {iEnumerator.Current}");
            }
            Console.ReadKey();
        }


        static IEnumerable<int> CreateIEnumerable()
        {
            Console.WriteLine($"{Padding} Start of CreateIEnumerable()");
            for (int i = 0; i < 3; i++)
            {
                Console.WriteLine($"{Padding}About to yield {i}");
                yield return i;//每次调用 iEnumerator.MoveNext() 方法时,代码执行到这里就停止,直到下一次调用 iEnumerator.MoveNext() 方法才会继续往下执行
                Console.WriteLine($"{Padding}After yield");
            }
            Console.WriteLine($"{Padding}Yielding final value");
            yield return -1;//整个代码不会在这里结束,而是通过调用 iEnumerator.MoveNext() 方法返回 false 时才结束.
            Console.WriteLine($"{Padding}End of CreateIEnumerable");
        }
    }

 

 

迭代器传参数

 

        static void Test()
        {
            foreach (var i in CreateIEnumerable(DateTime.Now.AddSeconds(2)))
            {
                Console.WriteLine(i);
                if (i > 3)
                {
                    Console.WriteLine("return");
                    return;
                }
                Thread.Sleep(300);
            }
        }



        static IEnumerable<int> CreateIEnumerable(DateTime stop)
        {
            try
            {
                for (int i = 0; i < 10000; i++)
                {
                    if (DateTime.Now >= stop)
                    {
                        Console.WriteLine($"i = {i}");
                        yield break;
                    }
                    yield return i;
                }
            }
            finally
            {
                Console.WriteLine("yield end");
            }
        }

 

posted @ 2018-03-11 00:11  热敷哥  阅读(172)  评论(0编辑  收藏  举报