.netframerwork中的枚举器

  我们可以使用foreach迭代数组,集合。那么为什么数组和集合可以使用foreach迭代而别的类型却不行呢?foreach的内部机制又是什么,这里我来说一下枚举器。

  很多教材上说,使用foreach迭代必须满足什么条件,很多人就是一句话,实现IEumerable接口。其实不是完全正确。实现IEumerable接口只是第一步。第二步是要实现GetEumerator()方法,这个方法需要返回IEumerator接口。其实真正起作用的是GetEumerator()和IEumerator接口。原因是,C#中的foreach最后在IL代码中不会编译为foreach,而是会把foreach转化为IEumnerator接口的方法和属性.先看看IEumerator的几个属性和方法。

  MoveNext():MoveNext方法移动到集合的下一个元素,如果有这个元素,返回true,如果集合不再有任何元素,返回false

  Current:返回光标所在的元素

  Reset():将光标重新定位与集合的开头

  我们来看看具体的代码是怎么实现的。

  首先我们定义一个集合,集合是实现了IEumerable接口,和实现了返回IEumerator的GetEumerator()方法。所以我们可以使用foreach迭代。

  还是实体类。

  [Serializable]
   public class Person
    {
        private int _personId;

        public int PersonId
        {
            get { return _personId; }
            set { _personId = value; }
        }

        private string _firstName;

        public string FirstName
        {
            get { return _firstName; }
            set { _firstName = value; }
        }

        private string _lastName;

        public string LastName
        {
            get { return _lastName; }
            set { _lastName = value; }
        }

        public Person() { }

        public Person(int id, string firstName, string lastName)
        {
            this._personId = id;
            this._firstName = firstName;
            this._lastName = lastName;
        }
    }

  调用代码

static void Main(string[] args)
        {
            List<Person> persons = new List<Person>();
            persons.Add(new Person(1,"Edrick","Liu"));
            persons.Add(new Person(2,"Meci","Luo"));
            persons.Add(new Person(3, "Jun", "Ma"));

            foreach (Person person in persons)
            {
                Console.WriteLine("PersonId:{0},FirstName:{1},LastName:{2}",person.PersonId,person.FirstName,person.LastName);   
            }
            Console.Read();
        }

  迭代了这个集合,实际上foreach在编译的时候会翻译成下面的代码

 static void Main(string[] args)
        {
            List<Person> persons = new List<Person>();
            persons.Add(new Person(1,"Edrick","Liu"));
            persons.Add(new Person(2,"Meci","Luo"));
            persons.Add(new Person(3, "Jun", "Ma"));

            IEnumerator<Person> eumerator = persons.GetEnumerator();

            try
            {
                while (eumerator.MoveNext())
                {
                    Person p = eumerator.Current;
                    Console.WriteLine("PersonId:{0},FirstName:{1},LastName:{2}", p.PersonId, p.FirstName, p.LastName);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                eumerator.Reset();
            }

            Console.Read();
        }

  foreach也只是这种方式的简便调用,那么我们该怎么实现IEumerable接口的GetEumerator()方法呢?下面我们来看看怎么实现。

  首先我们介绍一个关键字,yield关键字

  yield:在迭代器块中用于向枚举对象提供值或结束迭代。一般跟return和break配合使用

  yield return:返回集合中的一个元素,并移动到下一个元素。

  yield break:停止迭代

  那么什么是迭代块?

  迭代块:包含yield语句的方法或者属性称为迭代块,迭代块必须声明为返回IEumerator或者IEumerable。

  还是先看一个简单的示例

 public class StringCollection:IEnumerable
    {
        public IEnumerator GetEnumerator()
        {
            yield return "Edrick";
            yield return "Liu";
        }
    }

  调用代码

static void Main(string[] args)
        {
            StringCollection coll = new StringCollection();
            foreach (string s in coll)
            {
                Console.WriteLine(s);
            }
            Console.Read();
        }

这里其实还是我们先前介绍的,foreach的原理。那么迭代块是怎么运行的呢,为什么我们yield return的时候枚举器可以识别呢?

 public class StringCollection:IEnumerable
    {
        public IEnumerator GetEnumerator()
        {
            Enumererator en = new Enumererator(0);
            return en;
        }

        public class Enumererator : IEnumerator
        {
            private int state;
            private object current;

            public Enumererator(int state) { this.state = state; }

            object IEnumerator.Current
            {
                get { return current; }
            }

            bool IEnumerator.MoveNext()
            {
                switch (state)
                { 
                    case 0:
                        current = "Edrick";
                        state = 1;
                        return true;
                    case 1:
                        current = "Liu";
                        state = 2;
                        return true;
                    case 2:
                        break;
                }
                return false;
                
            }

            void IEnumerator.Reset()
            {
                throw new NotImplementedException();
            }
        }
    }

迭代块会编译成一个yield类型,就是我们这里定义的类Enumererator,继承自IEnumerator。 定义了一个状态机,一个元素。每次迭代一个元素,状态机会加1.然后把我们定义的值赋给 current。current有由Current返回。最后,在GetEnumerator()返回这个类。这就是yield的实现机制。所以,我们可以利用yield做一些别的事情

 public class MuTitle
    {
       string[] names = { "Edrick Liu","Meci Luo","Ma Jun","XiongKaiHeng"};
       public IEnumerator GetEnumerator()
       {
           for (int i = 0; i < names.Length; i++)
           { 
             yield return names[i];
           }
       }

       public IEnumerable Reverse()
       {
           for (int i = names.Length-1; i >=0; i--)
           {
               yield return names[i];
           }
       }

       public IEnumerable Subset(int index, int length)
       {
           for (int i = index; i < index + length; i++)
           {
               yield return names[i];
           }
       }
    }

上面有两个方法返回的是IEnumerable,是为了包装我们的枚举器。实际等效于

       public IEnumerable Reverse()
       {
           Enumerable e = new Enumerable();
           return e;
       }

       public class Enumerable :IEnumerable
       {

           public IEnumerator GetEnumerator()
           {
               MuTitle m = new MuTitle();
               for (int i = m.names.Length - 1; i >= 0; i--)
               {
                   yield return m.names[i];
               }
           }
       }

  

posted @ 2011-11-08 19:35  刘中栋  阅读(263)  评论(0)    收藏  举报