枚举聚合中的元素
之前我们写过这样的代码:

foreach极大简化了需要编写的代码,但是foreach只能在特定情况下使用-只能遍历可枚举集合.
什么是可枚举集合?就是实现了System.Collections.IEnumerable接口的集合

可以看到IEnumerable接口包含了一个名为GetEnumerator的方法:
GetEnumerator返回IEnumerator
也就是返回了实现了IEnumerator接口的枚举器对象

可将枚举器视为指向列表中的元素的指针.指针最开始指向第一个元素之前的位置。
调用MoveNext方法,就可以让指针移到列表中的下一项,移动成功返回true,否则返回false。
Current属性访问当前指向的项,Rest方法返回到指针第一项之前的位置。
使用集合的GetEnumerator方法创建枚举器,然后反复调用MoveNext方法,并获取Current属性的值,就可以每次在集合中移动一个元素的位置。这就是foreach语句做的事情。
为了创建自己的可枚举集合类,就必须在自己的集合类中实现IEnumerable接口,并提供IEnumerator该接口的一个实现.
以便由集合类的GetEnumerator方法返回.
下面来手动实现枚举器

代码如下:
static void InsertIntoTree<TItem>(ref Tree<TItem> tree, params TItem[] data) where TItem : IComparable<TItem>
{
foreach (TItem datum in data)
{
if (tree == null)
{
tree = new Tree<TItem>(datum);
}
else
{
tree.Insert(datum);
}
}
}
public class TreeEnumerator<TItem> : IEnumerator<TItem> where TItem : IComparable<TItem>
{
private Tree<TItem> currentData = null; //容纳对要枚举的树的引用
private TItem currentItem = default(TItem);//容纳Current属性返回的值,因为TItem是未知的,我们不知道用什么值来初始化它
//所以最好的方法就是使用default,如果是引用类型则为null,如果是数值就为0,如果是bool,则为false等等.
private Queue<TItem> enumData = null;// 用节点的值填充enumData队列
public TreeEnumerator(Tree<TItem> data) //构造器
{
this.currentData = data;
}
private void populate(Queue<TItem>enumQueue, Tree<TItem> tree)//填充方法
{
if(tree.LeftTree != null)
{
populate(enumQueue, tree.LeftTree);
}
enumQueue.Enqueue(tree.NodeData);
if(tree.RightTree != null)
{
populate(enumQueue, tree.RightTree);
}
}
TItem IEnumerator<TItem>.Current
{
get
{
if(this.enumData == null)//确定已经调用了一次MoveNext,因为枚举器最开始指向第一个元素之前的位置.
{
throw new InvalidOperationException();
}
return this.currentItem;
}
}
object IEnumerator.Current => throw new NotImplementedException();
void IDisposable.Dispose()
{
// throw new NotImplementedException();
}
bool IEnumerator.MoveNext()
{
if(this.enumData == null)
{
this.enumData = new Queue<TItem>();
populate(this.enumData, this.currentData);
}
if(this.enumData.Count > 0)
{
this.currentItem = this.enumData.Dequeue();
return true;
}
return false;
}
void IEnumerator.Reset()
{
throw new NotImplementedException();
}
}
public class Tree<TItem> : IEnumerable<TItem> where TItem : IComparable<TItem>
{
public TItem NodeData { get; set; }
public Tree<TItem> LeftTree { get; set; }
public Tree<TItem> RightTree { get; set; }
public Tree(TItem nodeValue) //构造器名称不能包含类型参数,它名为Tree
{
this.NodeData = nodeValue;
this.LeftTree = null;
this.RightTree = null;
}
public void Insert(TItem newItem)
{
TItem currentNodeValue = this.NodeData;
if (currentNodeValue.CompareTo(newItem) > 0) //比较当前节点的值和新项的值
{
if (this.LeftTree == null)
{
this.LeftTree = new Tree<TItem>(newItem);
}
else
{
this.LeftTree.Insert(newItem);
}
}
else //右子树
{
if (this.RightTree == null)
{
this.RightTree = new Tree<TItem>(newItem);
}
else
{
this.RightTree.Insert(newItem);
}
}
}
public string WalkTree() //树的遍历
{
string result = "";
if (this.LeftTree != null)
{
result = this.LeftTree.WalkTree();
}
result += $" {this.NodeData.ToString()}";
if (this.RightTree != null)
{
result += this.RightTree.WalkTree();
}
return result;
}
IEnumerator<TItem> IEnumerable<TItem>.GetEnumerator()
{
return new TreeEnumerator<TItem>(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
…
…
用迭代器实现枚举器
为了减轻程序员的负担,C#提供了迭代器来帮我们更简单的让集合变得可枚举.
迭代器是能生成已排序值序列的一个代码块。
一个简单的迭代器
class BasicCoolection<T> : IEnumerable<T>
{
private List<T> data = new List<T>();
public void FillList(params T[] items)
{
foreach(var datum in items)
{
data.Add(datum);
}
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
foreach(var datum in data)
{
yield return datum;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
注意GetEnumerator方法,它并不会返回IEnumerator<T>,相反,它遍历data数组中的各项,并依次返回每一项.重点在于yield关键字的使用.yiled关键词每次迭代要返回的值。
可这样理解yield语句:yield本意是放弃或让路,后因为一些语义的变化,有了生成、生产的意思。而yield return关键字表达的意思是,暂时让出控制权,返回(生成的)值.它临时将方法暂停,将一个值传回调用者。当调用者需要下一个值时,GetEnumerator方法就从上次暂停的地方继续,生成下一个值。
GetEnumerator定义了一个迭代器,编译器利用这些代码实现IEnumerator<T>接口,其中包含Current属性和MoveNext方法。


我们可以通过提供附加属性来实现IEnumerable接口,并用一个迭代器返回数据,实现按相反顺序输出。

…
使用迭代器为Tree<TItem>类定义枚举器
之前我们定义了TreeEnumerator<TItem> 来实现枚举器
现在我们可以使用迭代器来实现
IEnumerator<TItem> IEnumerable<TItem>.GetEnumerator()
{
if(this.LeftTree != null)
{
foreach(TItem item in this.LeftTree)
{
yield return item;
}
}
yield return this.NodeData;
if(this.RightTree != null)
{
foreach(TItem item in this.RightTree)
{
yield return item;
}
}
}

可以看到运行正常.
本文探讨了C#中的枚举概念,强调了foreach语句在可枚举集合中的应用。枚举器通过实现IEnumerable接口和IEnumerator接口使集合可遍历。文中详细解释了如何手动实现枚举器,特别提到了yield关键字在迭代器中的作用,它允许在运行时生成值,简化了枚举器的实现。此外,还展示了如何通过迭代器为类定义枚举器,以按不同顺序输出数据。
936

被折叠的 条评论
为什么被折叠?



