**我直接贴出代码,每个代码的作用我已经注释上,后面在单独分析下语法糖,一定要上机测试。
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集合的变量
///
/// 构造函数
///
///
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();
///
/// 实现枚举遍历接口
///
///
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();
}
///
/// 插入
///
///
///
public void Insert(int index, Person value)
{
_al.Insert(index, value);
}
///
/// 查询特定对象在集合中的位置
///
///
///
public int IndexOf(Person p)
{
return _al.IndexOf(p);
}
///
/// 按照索引下标删除制定对象
///
///
public void RemoveAt(int removeIndex)
{
_al.RemoveAt(removeIndex);
}
///
/// 删除指定对象
///
///
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吗,肯定不是,这个是面向程序员的,方便,但是真正面向计算机就是复杂的等同功能的代码。 所以啊微软给我们方便的同时也给了我们很多疑惑啊。