实现IEnumerator 已支持foreach

来源:互联网 发布:写安卓软件 编辑:程序博客网 时间:2024/06/10 08:46

      本文严重参考了 笔记:IEnumerable和IEnumerator(包括泛型版),  另外caozhy在BBS也有一段总结(链接地址), 可以参考


1.  一个对象可以用foreach遍历, 必须包含以下3个:
    一个GetEnumerator()方法, 无参数,返回类型任意
    Current属性,只有get方法,不能有set方法(编译器也不允许),返回类型任意
    MoveNext()方法,无参数, 判断是否到遍历完毕,返回类型bool
    为了让Current属性知道应该返回哪个值了, 和 辅助MoveNext判断是否遍历完毕, 通常还会有个 int index, 虽然不必须但很有必要

2.  实现了上面3个方法和属性, 代码就可以使用foreach 了, 编译器会自动去绑定,因此不需要IEnumerable和IEnumerator。
    其实接口的本质就是让编译器去绑定一个对象的方法,在这里,C#编译器的确不依赖接口
    //手动让一个int数组支持foreach    class A    {        int[] array = new int[]{1,2,3,4,5};        int index=-1;        public A GetEnumerator() { return this; }        public int Current { get { return array[index]; } }        public bool MoveNext() { index++; return index < array.Length ? true : false; }    }    class TaskThread    {        static void Main()        {            A a = new A();            foreach (var s in a)                Console.WriteLine(s);        }    }

3.  IEnumerable接口指明需要GetEnumerator()
    IEnumerator接口指明需要Current、MoveNext(), 但VS中点击 "实现接口", 通常还会生成Reset()方法.
    一般我们对一个数组变量连续2次使用 foreach, 就会循环两遍; 如果你仅实现了上面的三个方法和属性, 连续2次foreach却不能达到要求
    class A    {        int[] array = new int[]{1,2,3,4,5};        int index=-1;        public A GetEnumerator() { return this; }        public int Current { get { return array[index]; } }        public bool MoveNext()         {             index++;            if (index < array.Length)                return true;            else            {                //Reset();                return false;            }        }        //private void Reset()        //{        //    index = -1;        //}    }    class TaskThread    {        static void Main()        {            A a = new A();            foreach (var s in a)                Console.WriteLine(s);            Console.WriteLine("循环第二次");            foreach (var s in a)                Console.WriteLine(s);        }    }
    原因是: 第一次循环结束后, index就已经等于array.Length了; 第二次循环一开始, foreach会首先进行MoveNext()判断, 判断结果为false, 当然立马退出咯
    当把注释掉的 Reset() 方法恢复后, 就可以得到正常结果了.


4.  IEnumerable和IEnumerator通过IEnumerable的GetEnumerator()方法建立了连接,可以通过IEnumerable的GetEnumerator()得到IEnumerator对象
    那为什么集合类不直接继承(支持)IEnumerator<T>和IEnumerator接口?
    假如同时有两个循环交错遍历同一个集合(一个foreach嵌套了另外一个foreach),那么集合必须维持当前元素的一个状态指示器,确保当调用MoveNext()方法时,能正确定位下一个元素。
    为了解决这个问题,集合类不直接支持IEnumerator<T>和IEnumerator接口。而是通过IEnumerable的GetEnumerator返回一个新的IEnumerator对象来负责维护循环遍历的状态,IEnumerator(迭代器,或者叫枚举数)相当于一个“游标”(cursor)或者“书签”。可以有多个书签,移动每个书签都可独立于其他书签来遍历集合
下面的代码是双线程同时访问集合

    class A    {        int[] array = new int[]{1,2,3,4,5};        int index=-1;        public A GetEnumerator() { return this; }        public int Current { get { return array[index]; } }        public bool MoveNext()         {             index++;            if (index < array.Length)                return true;            else            {                Reset();                return false;            }        }        private void Reset()        {            index = -1;        }    }    class TaskThread    {        static A a = new A();        static void Main()        {            Thread m = new Thread(GetSum);            Thread n = new Thread(GetSum);            m.Start();            n.Start();        }        static void GetSum()        {            int sum = 0;            foreach (var s in a)            {                Thread.Sleep(100);  //模拟大量运算, 否则看不出效果                sum += s;            }            Console.WriteLine("ThreadID={0}, sum={1}",Thread.CurrentThread.ManagedThreadId,sum);        }

运行的结果是
ThreadID=3, sum=5
ThreadID=4, sum=26
按理正常结果应该是 15, 可以看出两个线程都分别干扰了对方的index


5.  由于IEnumerable<T>扩展(继承)了旧的IEnumerable接口,所以要实现两个不同的方法:
    IEnumerator<T> GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator; // 由于和泛型版本的方法同名,所以该方法的实现需要使用显式接口实现


6.  我们通过ILSpy 看看源码中 List 的IEnumerator定义

public struct Enumerator : IEnumerator<T>, IDisposable, IEnumerator{private List<T> list;private int index;private int version;private T current;[__DynamicallyInvokable]public T Current{[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]get{return this.current;}}[__DynamicallyInvokable]object IEnumerator.Current{[__DynamicallyInvokable]get{if (this.index == 0 || this.index == this.list._size + 1){ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);}return this.Current;}}internal Enumerator(List<T> list){this.list = list;this.index = 0;this.version = list._version;this.current = default(T);}[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]public void Dispose(){}[__DynamicallyInvokable]public bool MoveNext(){List<T> list = this.list;if (this.version == list._version && this.index < list._size){this.current = list._items[this.index];this.index++;return true;}return this.MoveNextRare();}private bool MoveNextRare(){if (this.version != this.list._version){ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);}this.index = this.list._size + 1;this.current = default(T);return false;}[__DynamicallyInvokable]void IEnumerator.Reset(){if (this.version != this.list._version){ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);}this.index = 0;this.current = default(T);}}




后记: 我现在很疑惑非要实现这两个接口,  for (int i = 0; i < length; i++) 这种遍历方式的速度还要快些, 如果不是泛型, 比如int[]数组, 即便是用了foreach, 编译器也会优化成for循环, 那实现这两个接口仅仅是为了让别人用 foreach ?




0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 4s锁屏密码忘了怎么办 6p触屏偶尔乱跳怎么办 新办劳务公司的劳务资质怎么办 汽车没电了打不着火怎么办 吃凉的甜的牙疼怎么办 学车对点对不上怎么办 发现老公有外遇最明智的怎么办 想开个童装店但是没经验怎么办 母乳不够吃宝宝又不喝奶粉怎么办 掉头发很厉害怎么办有什么偏方 红米2a刷死机了怎么办 公司退市我们买的股票怎么办 黑魂3把npc杀了怎么办 摔倒了膝盖摔肿了又痛怎么办 厕所堵了怎么办疏通马桶有妙招 月经不来怎么办如何让它快点来 苹果手机进水了开不了机怎么办 苹果5s进水了怎么办修要多少钱 吃了过期3年的药怎么办 离婚后孩子的抚养费不给怎么办 小车钥匙丢了怎么办配要多少钱 一键启动的车钥匙丢了怎么办 股票退市了手里的股票怎么办 3d硬金以后要换怎么办 偏指甲红肿长在了肉里怎么办 牙齿黄怎么办教你牙齿美白小窍门 2岁孩门牙磕断了怎么办 脸上用了含激素的产品应该怎么办 身上起红疙瘩水泡很痒怎么办 怀疑老公有外遇他不承认怎么办 苹果手机锁屏密码忘了怎么办 黑色t恤洗了掉毛怎么办 老婆要和我离婚我该怎么办 发现老婆有外遇最明智的怎么办 高度近视怎么办我快一千度近 天刀耐久度为0了怎么办 新车被4s店装了怎么办 h面和w面联系线怎么办 造梦西游3到80级怎么办 加95的车加了92怎么办 95的车加92的油怎么办