参考书:《 visual C# 从入门到精通》
第三部分 用C#定义可扩展类型
第19章 枚举集合
文章目录
19.1 枚举集合中的元素
我们前面用foreach
语句来方便的遍历集合,实际上foreach
语句只能遍历可枚举集合,就是实现了System.Collections.IEnumerable
接口的集合。IEnumerable
接口中包含一个名为GetEnumerator
的方法,返回实现该接口的枚举器对象。枚举器对象用于遍历集合中的元素。IEnumerator
接口指定了一下属性和方法:
object Current{get;}
bool MoveNext();
void Reset();
将枚举器看成指针,最开始指向第一个元素之前的位置,然后调用MoveNext
方法,指针成功移动到下一项则返回true
,否则返回false
。用Current
访问当前指向的那一项,用Reset
方法使指针返回到第一项的前一项位置。这样用集合的GetEnmerator
方法创建枚举器,然后反复调用MoveNext
,获取Current
的值,这个过程正是foreach
语句 做的事情。当然类库也提供了泛型IEnumerable<T>
接口。
19.1.1 手动实现枚举器
在VS中新建一个控制台应用程序,添加一个源文件为第17章的二叉树类Tree.cs
,然后我们创建一个TreeEnumerator.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace c_19_1_1
{
class TreeEnumerator<TItem>: IEnumerator<TItem> where TItem:IComparable<TItem>
{
private Tree<TItem> currentData = null;
private TItem currentItem = default(TItem);
private Queue<TItem> enumData = null;
TItem IEnumerator<TItem>.Current
{
get
{
if (this.enumData == null)
throw new InvalidOperationException("Use MoveNext before calling Current");
return this.currentItem;
}
}
object IEnumerator.Current => throw new NotImplementedException();
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);
}
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();
}
#region IDisposable Support
private bool disposedValue = false; // 要检测冗余调用
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: 释放托管状态(托管对象)。
}
// TODO: 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。
// TODO: 将大型字段设置为 null。
disposedValue = true;
}
}
// TODO: 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。
// ~TreeEnumerator()
// {
// // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
// Dispose(false);
// }
// 添加此代码以正确实现可处置模式。
void IDisposable.Dispose()
{
// 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
//Dispose(true);
// TODO: 如果在以上内容中替代了终结器,则取消注释以下行。
// GC.SuppressFinalize(this);
}
#endregion
}
}
生成解决方案,成功就说明程序没有明显的语法错误。
19.1.2 实现IEnumerable
接口
将源文件Tree.cs
中的代码稍作修改:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace c_19_1_1
{
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)
{
this.NodeData = nodeValue;
this.LeftTree = null;
this.RightTree = null;
}
public void Insert(TItem newItem)
{
TItem currentNodeValuie = this.NodeData;
if (currentNodeValuie.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();
}
}
}
在Program.cs
中测试枚举器:
using System;
namespace c_19_1_1
{
class Program
{
static void Main(string[] args)
{
Tree<int> tree1 = new Tree<int>(10);
tree1.Insert(5);
tree1.Insert(11);
tree1.Insert(5);
tree1.Insert(-12);
tree1.Insert(15);
tree1.Insert(0);
tree1.Insert(14);
tree1.Insert(-8);
tree1.Insert(10);
foreach(int item in tree1)
{
Console.WriteLine(item);
}
}
}
}
运行结果:
-12
-8
0
5
5
10
10
11
14
15
C:\Users\xhh\Source\Repos\c_19_1_1\c_19_1_1\bin\Debug\netcoreapp3.1\c_19_1_1.exe (进程 26196)已退出,代码为 0。
按任意键关闭此窗口. . .
19.2 用迭代器实现枚举器
迭代器是能生成已排序值序列的一个代码块。
19.2.1 一个简单的迭代器
class BasicCollection<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 daturm;
}
}
IEnumerator IEnumerable.GetEnumerator(){
throw new notImplementedException();
}
}
yield
的使用很关键,它指定每一次迭代要返回的值。GetEnumerator
方法中的代码定义了一个迭代器。编译器利用这些代码实现IEnumerator<T>
接口,其中包括Current
和MoveNext
。可以采取和平常一样的方式调用迭代器生成的枚举器:
BasicCollection<string>bc=new BasicCollection<string>();
bc.FillList("Twas","bring","and","the","slithy","toves");
foreach(string word in bc){
Console.WriteLine)(word);
}
还可以通过附加属性实现按相反顺序获取数据:
class BasicCollection<T>:IEnumerable<T>{
...;
public IEnumerable<T>Reverse{
get{
for(int i=data.Count-1;i>=0;--i){
yield return data[i];
}
}
}
}
调用该属性:
BasicCollection<string> bc=new BasicCollection<string>();
bc.FillList("Twas","bring","and","the","the","slithy","toves");
foreach(string word in bc.Reverse)
Console.WriteLine(word);
19.2.2 使用迭代器为Tree<TItem>类定义枚举类
回到我们前面的程序,将Tree.cs
中的IEnumerable<TItem>.GetEnumerator
方法改为:
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;
}
然后其它的都不变,运行一下发现能正常运行。