添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

枚举聚合中的元素

之前我们写过这样的代码:
在这里插入图片描述
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 ) ; 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 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 ) ; this . LeftTree . Insert ( newItem ) ; else //右子树 if ( this . RightTree == null ) this . RightTree = new Tree < TItem > ( newItem ) ; 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# in depth》的过程中,发现自己c#语言基础不够扎实,决定快速阅读一下《c#图解教程》。最近读到第19章学习泛型接口时,原本打算写一个简单的例子,结果搞了半天才成功。 我想很多读者,尤其是C#初学者也会有这个困扰,把这个我写的简单例子给大家分享一下吧。 class ColorsTemplateEnumerator<T>: IEnumerator<T> T[] colorsList; int position 有些新手说我写的比较复杂,想要点简单又实用的东西,而是就写下了这篇文章...,老鸟直接跳过. 在很项目中都涉及到事件监听及任务处理的代码,例如:想监听一个网络状态,就像QQ一样,一但网线一拨,它马上就弹出连接网络失败类似的东东,虽然我没深入了解QQ用的是什么原理,但下面的代码却可以实现一样的功能,各位如有更好的方法,欢迎讨论。 先上一段监听属性值的的代码 新建一个DoEven... 有一个枚举实现了这个接口 在这个枚举类中定义了四个基本类,加减乘除,但我们有时候还有别的算法列入求x的y次幂,x取余y,这个时候我们不方便扩展基础枚举,我们可以再声明一个枚举接口进行扩展就行: 在此直接对枚举进行扩展,我们就可以让枚 在C#中,如果一个类要使用foreach结构来实现迭代,就必须实现IEnumerableIEnumerator接口。其中,IEnumerator接口定义实现枚举模式的方法IEnumerator.MoveNext()和IEnumerator.Reset()和成员属性IEnumerator.Count,而IEnumerable接口的唯一方法IEnumerable.GetEnumerato...