c#之通过自定义集合彻底搞懂foreach这个语法糖

来源:互联网 发布:一元抽奖的软件是什么 编辑:程序博客网 时间:2024/06/08 16:48

**我直接贴出代码,每个代码的作用我已经注释上,后面在单独分析下语法糖,一定要上机测试。
using System;
using System.Collections;
using System.Collections.Generic;

using System.Linq;using System.Text;using System.Threading.Tasks;namespace 基本功修炼{    class MyEnnumberator : IEnumerator //自定义迭代接口  继承于IEnumerator 成为子类     {        private int i = 0; //索引下标,用于遍历集合中的元素        private ArrayList _al; //保存ArrayList集合的变量        /// <summary>        /// 构造函数        /// </summary>        /// <param name="al"></param>        public MyEnnumberator(ArrayList al) //自定迭代器的构造函数,并且接受外部传来的一个集合,保存在当前字段里        {            _al = al;        }        //        // 摘要:        //     获取集合中的当前元素。        //        // 返回结果:        //     集合中的当前元素。        public object Current        {            get            {                return _al[i++]; //返回当前索引对象的person元素,并且索引+1,这样下次返回就下个元素            }        }        //        // 摘要:        //     将枚举数推进到集合的下一个元素。        //        // 返回结果:        //     如果枚举数已成功地推进到下一个元素,则为 true;如果枚举数传递到集合的末尾,则为 false。        //        // 异常:        //   T:System.InvalidOperationException:        //     创建枚举器后,已修改该集合。        public bool MoveNext() //用来判断当前集合中还有没有可遍历输出的元素不        {            if(i > _al.Count - 1)             {                return false;            }else            {                return true;            }        }        //        // 摘要:        //     将枚举数设置为其初始位置,该位置位于集合中第一个元素之前。        //        // 异常:        //   T:System.InvalidOperationException:        //     创建枚举器后,已修改该集合。        public void Reset()        {            i = 0; //索引归为0  下次访问的时候就是从第一个元素开始        }    }    class Person //数据类型,作为存入集合中的测试数据元素    {        public string name; //字段  用于输出测试用        public Person(string name) //构造函数        {            this.name = name;        }        public override string ToString() //重写ToString为了输出这个类的字段名称而不是自带的输出类名和名称空间        {            return name;        }    }    class PersonCollection : IEnumerable //表示可遍历可枚举    {        private ArrayList _al = new ArrayList();        /// <summary>        /// 实现枚举遍历接口        /// </summary>        /// <returns></returns>        public IEnumerator GetEnumerator() //实现的一个接口,通过这个方法即可让当前集合使用foreach这个语法糖,注释掉这个方法不能用foreach        {            return new MyEnnumberator(_al);  //这个是使用了里氏替换原则,子类可以当父类使用        }        //索引器        public Person this[int index]        {            get            {                return _al[index] as Person;            }        }        public void Add(Person p) //往当前集合添加一个人的元素        {            _al.Add(p); //内部用的是ArrayList的添加方法        }        public void Clear()        {            _al.Clear();        }        /// <summary>        /// 插入        /// </summary>        /// <param name="index"></param>        /// <param name="value"></param>        public void Insert(int index, Person value)        {            _al.Insert(index, value);        }        /// <summary>        /// 查询特定对象在集合中的位置        /// </summary>        /// <param name="p"></param>        /// <returns></returns>        public int IndexOf(Person p)        {            return _al.IndexOf(p);        }        /// <summary>        /// 按照索引下标删除制定对象        /// </summary>        /// <param name="removeIndex"></param>        public void RemoveAt(int removeIndex)        {            _al.RemoveAt(removeIndex);        }        /// <summary>        /// 删除指定对象        /// </summary>        /// <param name="p"></param>        public void Remove(Person p)        {            if(_al.Contains(p))            {                int intPostion = IndexOf(p);                RemoveAt(intPostion);            }        }    }    class Program    {        static void Main(string[] args)        {            PersonCollection collection = new PersonCollection();            //测试add方法            collection.Add(new Person("马天宇"));            collection.Add(new Person("马天"));            collection.Add(new Person("雄霸"));            Console.WriteLine(collection[0]);            collection.Insert(0, new Person("插队的大佬"));//测试插入功能            Console.WriteLine(collection[0]);            Console.WriteLine("\n开始遍历输出集合当中的元素:");            foreach (var item in collection) //测试foreach功能            {                Console.WriteLine(item);            }        }    }}

当你们看到这来的时候我希望你们已经运行代码完毕,如果没有上代码到VS没多少效果
那么现在我想你们最疑惑的是foreach到达是怎么迭代的。我想最简单的办法就是下断点调试
下面我在182行处断点
看截图
这里写图片描述
这里写图片描述
现在我单步调试已经要执行183行,那么第一步是做什么,肯定是要 得到collection,然后会从collection中寻找这个方法 public IEnumerator GetEnumerator() 没有找到就会报错找到后就执行
这里写图片描述
大家仔细看流程已经跑到这里,现在马上要跑 MyEnnumberator(_al)构造函数,并且要得到外部送进来的ArrayList集合,
这里写图片描述
大家好好看,通过构造函数的命令,操作系统就会分配好内存,然后马上会执行构造函数里面的代码给我们的自定义集合类指定ArrayList集合用于存储人这个数据,
这里写图片描述
现在在执行代码 看看往哪里跑;
这里写图片描述
看的出来执行完自定义迭代器构造函数之后会马上回调PersonCollection这里,开始接受也是得到迭代器对象,这样forech那边就拿到了迭代器。完成准备工作。
继续执行会跑到这个代码中判断集合还有没有元素
这里写图片描述
如果有的话,就会执行
这里写图片描述
内部代码就会自动给你选择一个元素塞在这个item变量当中,然后执行输出
这里写图片描述
还是被屏蔽了得到元素的细节,其实吧我们对这个得到元素的细节就不要深究了。我们知道必须得到一个迭代器的准备工作才可以开始,那么我们就必须先自定义一个迭代器 和实现一个必须实现的接口就可以。
IEnumerator IEnumerable 实现他们当中的全部接口 并且注意是返回
public IEnumerator GetEnumerator() //实现的一个接口,通过这个方法即可让当前集合使用foreach这个语法糖,注释掉这个方法不能用foreach
{
return new MyEnnumberator(_al); //这个是使用了里氏替换原则,子类可以当父类使用

}

注意最内部的集合是ArrayList,并且这个集合是自定义迭代器类里面new出来的,然后传给定义集合对象当中。**

好像还不能看清forea到达做了什么,那么我们不用for和forech该怎么输出集合对象保存的元素呢。
下面我针对HashTable做个试验

这里写图片描述

大家好好对比下,上机测试啊。forech就是 微软为了方便我们用的语法糖,如果我们用下面的代码去遍历迭代是不是没有forech清晰啊。所以我们就要求我们需要迭代的集合必须继承具体接口,就是因为用到的代码是确定的。

是不是还没搞清为什么我们在进行foreach的时候,我们不能对集合元素进行删除或者增加的,
首先我们要先学会用其他的代码来代替foreach这个语法糖, 比如我们用图的下面代码,如果我们在加上在循环里检测集合元素是不是变了,如果变了就抛一个InvaidOperationException 表示我们的操作是无效的,这样就解决了我们的疑问吗,还有一点就是编译器在编译代码之后,你觉得还是foreach吗,肯定不是,这个是面向程序员的,方便,但是真正面向计算机就是复杂的等同功能的代码。 所以啊微软给我们方便的同时也给了我们很多疑惑啊。