C#枚举数和迭代器

大道至简,始终认为简洁是一门优秀的编程语言的一个必要条件。相对来说,C#是比较简洁的,也越来越简洁。在C#中,一个关键字或者语法糖在编译器层面为我们做了很多乏味的工作,可能实现的是一个设计模式,甚至是一个算法。例如:lock关键字让用对象获取互斥锁从而实现线程同步,本质上是通过Monitor类来实现的,显然简洁很多。本文要讲的枚举数和迭代器在.net集合类被广泛使用,当然遵循着简洁的设计思想。

1.枚举数

1.1foreach的本质

我们知道,实现了IEnumerable接口的类型对象是可foreach遍历的,那么本质是什么呢?原来,在IEnumerable接口中定义这样一个方法:IEnumerator GetEnumerator(),

IEnumerator接口定义如下:

public interface IEnumerator
    {
        // 摘要:
        //     获取集合中的当前元素。
        //
        // 返回结果:
        //     集合中的当前元素。
        //
        // 异常:
        //   System.InvalidOperationException:
        //     枚举数定位在该集合的第一个元素之前或最后一个元素之后。
        object Current { get; }

        // 摘要:
        //     将枚举数推进到集合的下一个元素。
        //
        // 返回结果:
        //     如果枚举数成功地推进到下一个元素,则为 true;如果枚举数越过集合的结尾,则为 false。
        //
        // 异常:
        //   System.InvalidOperationException:
        //     在创建了枚举数后集合被修改了。
        bool MoveNext();
        //
        // 摘要:
        //     将枚举数设置为其初始位置,该位置位于集合中第一个元素之前。
        //
        // 异常:
        //   System.InvalidOperationException:
        //     在创建了枚举数后集合被修改了。
        void Reset();
    }
View Code

通过GetEnumerator方法,IEnumerable接口类型对象可以按需获取一个枚举数对象,枚举数可依次返回请求的集合元素作为迭代变量。

1.2枚举数的几种形式

上面说到实现了IEnumerable接口的类型对象是可foreach遍历的,这是充分不必要条件,实际上实现了IEnumerator GetEnumerator()方法的类型对象都是可枚举的。这里一共有三种形式:

  • IEnumerator/IEnumerable非泛型接口形式
  • IEnumerator<T>/IEnumerable<T>泛型接口形式
  • 自实现形式

1.2.1IEnumerator/IEnumerable非泛型接口形式

 我们来简单实现一下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            EnumerableEx arr = new object[] { "jello", 22, 'M' };
            foreach (var item in arr)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
        }
    }

    class EnumerableEx : IEnumerable
    {
        object[] arr;

        public static implicit operator EnumerableEx(object[] _arr)
        {
            EnumerableEx _enum = new EnumerableEx();
            _enum.arr = new object[_arr.Length];
            for (int i = 0; i < _arr.Length; i++)
            {
                _enum.arr[i] = _arr[i];
            }
            return _enum;
        }

        public IEnumerator GetEnumerator()
        {
            return new EnumeratorEx(arr);
        }
    }

    class EnumeratorEx : IEnumerator
    {
        private int _pos = -1;//当前元素位置
        private object[] _array;//要遍历的数组
        
        //构造函数
        public EnumeratorEx(object[] array)
        {
            _array = array;
        }
        //迭代变量
        public object Current
        {
            get 
            {
                if (_pos == -1 || _pos >= _array.Length)
                    throw new InvalidOperationException();
                return _array[_pos];
            }
        }
        //移位
        public bool MoveNext()
        {
            if (_pos < _array.Length - 1)
            {
                _pos++;
                return true;
            }
            else
                return false;
        }
        //重置
        public void Reset()
        {
            _pos = -1;
        }
    }

}
View Code

这里首先向IEnumerable提供了需遍历的数组(使用了隐式用户自定义转换),在foreach中首先会调用GetEnumerator方法,然后MoveNext移到下一个位置,Current即为迭代变量。

1.2.2IEnumerator<T>/IEnumerable<T>泛型接口形式

非泛型接口形式中迭代变量是Object类型(非类型安全),这无法避免装箱和拆箱,尤其是当元素个数很多的时候,性能会消耗很大,因此引入了泛型接口形式。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program1
    {
        public static void Main(string[] args)
        {
            EnumerableEx<int> arr = new int[] { 1, 2, 3 };
            foreach (var item in arr)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
        }
    }

    class EnumerableEx<T> : IEnumerable<T>
    {
        T[] arr;

        public static implicit operator EnumerableEx<T>(T[] _arr)
        {
            EnumerableEx<T> _enum = new EnumerableEx<T>();
            _enum.arr = new T[_arr.Length];
            for (int i = 0; i < _arr.Length; i++)
            {
                _enum.arr[i] = _arr[i];
            }
            return _enum;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new EnumeratorEx<T>(arr);
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return new EnumeratorEx<T>(arr);
        }
    }


    class EnumeratorEx<T> : IEnumerator<T>
    {
        private int _pos = -1;//当前元素位置
        private T[] _array;//要遍历的数组

        public EnumeratorEx(T[] array)
        {
            _array = array;
        }

        public T Current
        {
            get
            {
                if (_pos == -1 || _pos >= _array.Length)
                    throw new InvalidOperationException();
                return _array[_pos];
            }
        }

        public void Dispose()
        {
            //可用于释放非托管资源
        }

        object System.Collections.IEnumerator.Current
        {
            get 
            {
                if (_pos == -1 || _pos >= _array.Length)
                    throw new InvalidOperationException();
                return _array[_pos];
            }
        }

        public bool MoveNext()
        {
            if (_pos < _array.Length - 1)
            {
                _pos++;
                return true;
            }
            else
                return false;
        }

        public void Reset()
        {
            _pos = -1;
        }
    }

}
View Code

和非泛型接口形式基本一样,IEnumerable<T>除了继承IEnumerable接口,还继承了IDisposable接口用来释放非托管资源。

1.2.3自实现形式

自实现形式不继承自上面的接口,自定义一个实现GetEnumerator()的类和一个实现Current和MoveNext的类,好处是更加灵活,缺点是通用性差。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace ConsoleApplication1
{
    class Program2
    {
        public static void Main(string[] args)
        {
            MyEnumerable<int> arr = new int[] { 1, 2, 3 };
            foreach (var item in arr)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
        }
    }
    class MyEnumerable<T>
    {
        T[] arr;

        public static implicit operator MyEnumerable<T>(T[] _arr)
        {
            MyEnumerable<T> _enum = new MyEnumerable<T>();
            _enum.arr = new T[_arr.Length];
            for (int i = 0; i < _arr.Length; i++)
            {
                _enum.arr[i] = _arr[i];
            }
            return _enum;
        }

        public MyEnumerator<T> GetEnumerator()
        {
            return new MyEnumerator<T>(arr);
        }
    }
    class MyEnumerator<T>
    {
        private int _pos = -1;//当前元素位置
        private T[] _array;//要遍历的数组

        public MyEnumerator(T[] array)
        {
            _array = array;
        }

        public T Current
        {
            get
            {
                if (_pos == -1 || _pos >= _array.Length)
                    throw new InvalidOperationException();
                return _array[_pos];
            }
        }

        public bool MoveNext()
        {
            if (_pos < _array.Length - 1)
            {
                _pos++;
                return true;
            }
            else
                return false;
        }

        public void Reset()
        {
            _pos = -1;
        }
    }
}
View Code

需要注意的是:Reset方法并不是必须要实现的。

2.迭代器

2.1What's 迭代器

迭代器是在.net2.0中引入的一种结构,旨在更加简单地创建枚举数和可枚举类型。先来看一个简单例子:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program3
    {
        public static void Main(string[] args)
        {
            Iteration iteration = new Iteration();
            foreach (var item in iteration)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
        }
        
    }
    class Iteration
    {
        public IEnumerator<int> GetNums()
        {
            for (int i = 0; i < 10; i++)
            {
                yield return i;
            }
        }

        public IEnumerator<int> GetEnumerator()
        {
            return GetNums();
        }
    }
}
View Code

上面是通过yield return来获取枚举数的,通过运行结果发现,循环体内的yield return并没有在第一次迭代中返回,而是每次访问迭代变量时都能获取一个新元素值。

由一个或多个yield语句组成的代码块称为迭代器块,它和普通的代码块不同,并不是依次执行的,仅当需要获取迭代变量值时执行一次。

上面举了一个使用迭代器来创建枚举数的例子,其实,使用迭代器还可以创建可枚举类型:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program3
    {
        public static void Main(string[] args)
        {
            Iteration iteration = new Iteration();
            foreach (var item in iteration)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
        }
        
    }
    class Iteration
    {
        public IEnumerable<int> GetNums()
        {
            for (int i = 0; i < 10; i++)
            {
                yield return i;
            }
        }

        public IEnumerator<int> GetEnumerator()
        {
            return GetNums().GetEnumerator();
        }
    }
}
View Code

上面的两段代码有两个地方的不同:一是GetNums方法返回类型不同;二是GetEnumerator方法实现的不同。

2.2迭代器本质

我们使用简单的yield return就可以创建枚举数或可枚举类型,那么在编译器层面究竟做了些什么呢?通过IL代码可以管中窥豹:

原来,编译器在遇到迭代器块时会生成一个嵌套类,这个类实现了IEnumerable<T>和IEnumerator<T>等接口,在这个类中维护了一个拥有四个状态的状态机:

  1. Before:首次调用MoveNext的初始状态
  2. Running:调用MoveNext后进入该状态。在这个状态中,枚举数检测并设置下一项的位置。在遇到yield return、yield break或迭代器块结束时退出状态
  3. Suspended:状态机等待下次调用MoveNext的状态
  4. After:没有更多项可以枚举

如果状态机在Before或Suspended状态有一次MoveNext调用就进入Running状态。在Running状态中检测集合的下一项并设置位置。如果有更多项状态机会转入Suspended状态,如果没有更多项则转入并保持在After状态,如图所示:

3.总结

总而言之,其实是对迭代器设计模式的运用简化。既不暴露集合的内部结构,又可让外部代码透明的访问集合内部的数据,这是迭代器设计模式的思想。

posted @ 2014-10-04 08:43  jello chen  阅读(629)  评论(0编辑  收藏  举报