• 迭代器


    引言:

       在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");
                }
            }
  • 相关阅读:
    写了一个Windows服务,通过C#模拟网站用户登录并爬取BUG列表查询有没有新的BUG,并提醒我
    WCF快速上手(二)
    Oracle递归查询
    100多行代码实现6秒完成50万条多线程并发日志文件写入
    C#写日志工具类
    WPF定时刷新UI界面
    HttpUtil工具类
    WPF GridView的列宽度设置为按比例分配
    Visifire图表
    C# BackgroundWorker 的使用、封装
  • 原文地址:https://www.cnblogs.com/refuge/p/8542856.html
Copyright © 2020-2023  润新知