C#中可枚举类型详解
枚举是迭代一个集合中的数据项的过程。
我们经常使用的大多数集合实际上都已经实现了枚举的接口IEnumerable和IEnumerator接口,这样才能使用foreach迭代,有些是含有某种抽象了枚举细节的接口:ArrayList类型有索引,BitArray有Get方法,哈希表和字典有键和值..........其实他们都已经实现了IEnumerable和IEnumerator接口。所以一切的集合和数组都可以用IEnumerable或者IEnumerable
IEnumerablelists1=newint[]{3,4,5}; foreach(varvalinlists1) { Console.WriteLine(val); } IEnumerablelists2=newint[]{1,2,3}; foreach(varvalinlists2) { Console.WriteLine(val); }
下面讲解一下自己来定义可枚举类型(简单说就是自己定义的,可以进行foreach迭代的集合):
因为枚举非常有好处,可以消除很多的错误,所以实现某种标准是有好处的。这种标准就是IEnumerable和IEnumerator接口,必须实现了它才能够使用foreach迭代,才能真正算是一个自己定义的,功能健全的集合。
我们自己建立的可枚举类型必须实现IEnumerable和IEnumerator接口(其实两者都有一个泛型实现)。
IEnumerable接口含有一个方法,该方法返回一个枚举器对象,枚举器对象实现了IEnumerator接口(实际上可以认为继承和实现了IEnumerator的接口的类的对象就是枚举器对象),可以用它来进行迭代。
下面是两个接口的定义(系统早已经定义好):
publicinterfaceIEnumerable { IEnumeratorGetEnumerator(); }
该接口只有一个GetEnumerator的方法,返回一个枚举器,用于枚举集合中的元素。
publicinterfaceIEnumerator { objectCurrent{get;};//Current属性返回集合的当前元素 boolMoveNext();//将枚举移动到下一位 voidReset();//使枚举回到开头 }
凡是继承和实现了上面这个接口的类对象就是枚举器,可以利用上面的三个方法进行枚举,非常安全。不过需要自己在继承了接口的代码中去写实现过程。
一般的情况是:枚举器是枚举模式的一部分,通常被实现为枚举类型(继承IEnumerable)的一个嵌套类(继承IEnumerator)。嵌套类的好处就是可以访问外部类的私有成员,不破坏封装的原则。
下面我们自己来定义一个枚举类型,代码如下:
publicclassSimpleCollection:IEnumerable { //定义一个数组的字段 privateobject[]array; //定义一个构造函数 publicSimpleCollection(object[]items) { array=items; } //实现IEnumerable接口的GetNumerator方法该方法返回一个继承IEnumerator接口的类的实例 publicIEnumeratorGetEnumerator() { returnnewEnumerator(array); } //定义一个嵌套类来继承IEnumerator的接口 publicclassEnumerator:IEnumerator { //定义一个标记字段 privateintflag; //定义一个数组的字段 privateobject[]elements=null; //定义一个构造函数 publicEnumerator(object[]items) { elements=items; flag=-1;//将标记位初始化 //也可以采用下面的方法 //elements=newobject[items.Length]; //Array.Copy(items,elements,items.Length);//此静态方法用于将一个数组中的元素复制到另外一个数组 } //实现IEnumerator接口的Current属性;此属性返回集合的当前元素,是只读的 publicobjectCurrent { get { if(flag>elements.Length-1)thrownewInvalidOperationException("枚举已经结束"); elseif(flag<0)thrownewInvalidOperationException("枚举尚未开始"); elsereturnelements[flag]; } } //实现IEnumerator接口的MoveNext方法将枚举移动到下一位 publicboolMoveNext() { ++flag; if(flag>(elements.Length-1))returnfalse; elsereturntrue; } //实现IEnumerator接口的Reset方法使枚举回到开头 publicvoidReset() { flag=-1; } }
下面来延时如何使用枚举类型:
//下面来看枚举类型的使用 SimpleCollectioncollection=newSimpleCollection(newobject[]{1,2,3,4,5}); //使用方法 //接口变量名=继承了该接口的类的实例 IEnumeratorenumrator=collection.GetEnumerator(); while(enumrator.MoveNext()) { Console.WriteLine(enumrator.Current); } Console.ReadKey();
SimpleCollectionsimple=newSimpleCollection(newobject[]{1,2,3,4,5,6}); IEnumeratorenumerator=simple.GetEnumerator(); while(enumerator.MoveNext()) { Console.WriteLine(enumerator.Current); } //最重要的是,实现了那两个接口,我们就可以对我们的集合使用foreach迭代了,看下面 foreach(varsinsimple) { Console.WriteLine(s); }
下面给出两个接口的泛型实现:
首先需要注意的是:
1.IEnumerable
2.IEnumerator
代码如下:
////下面创建一个可枚举的泛类型 //首先该类型必须要继承IEnumerable接口 //因为IEnumerable 接口继承IEnumerable接口所以必须同时实现泛型和非泛型的GetEnumerator方法 publicclassSimpleCollection :IEnumerable { privateT[]array; publicSimpleCollection(T[]items) { array=items; } //实现IEnumerable 接口的GetNumerator方法该方法返回一个继承IEnumerator接口的类的实例 publicIEnumerator GetEnumerator() { returnnewEnumerator (array);//这步需要重视 } //为了避免混淆在此显式实现非泛型的接口 IEnumeratorIEnumerable.GetEnumerator() { returnnewEnumerator (array);//这步需要重视 } //定义一个嵌套类来继承IEnumerator 的接口 //IEnumerator 接口继承自IDisposable和IEnumerator接口 //该接口的唯一成员是Current属性但是同时也要实现其非泛型版本!!! //另外还需要实现IDisposable的Dispose方法和IEnumerator的两个方法 publicclassEnumerator<_T>:IEnumerator<_T> { privateintflag; private_T[]elements=null; publicEnumerator(_T[]items) { elements=items; flag=-1; } //实现IEnumerator 接口的Current属性;此属性返回集合的当前元素,是只读的 public_TCurrent { get { if(flag>elements.Length-1)thrownewInvalidOperationException("枚举已经结束"); elseif(flag<0)thrownewInvalidOperationException("枚举尚未开始"); elsereturnelements[flag]; } } //为了避免混淆显示实现IEnumerator接口的Current属性 objectIEnumerator.Current { get{returnCurrent;}//直接返回上面的泛型属性比较经典 } //实现IDisposable接口的Dispose方法支持确定性垃圾回收将枚举数的状态设置为after也就是把标记位设为最大索引+1 publicvoidDispose() { flag=elements.Length+1; } //实现IEnumerator接口的MoveNext方法将枚举移动到下一位 publicboolMoveNext() { ++flag; if(flag>(elements.Length-1))returnfalse; elsereturntrue; } //实现IEnumerator接口的Reset方法使枚举回到开头 publicvoidReset() { flag=-1; } }
怎么使用呢:
SimpleCollectioncolletion=newSimpleCollection (newstring[]{"ranran","Huaran"}); IEnumerator enumorator=colletion.GetEnumerator(); while(enumorator.MoveNext()) { Console.WriteLine(enumorator.Current); } foreach(varvincolletion) { Console.WriteLine(v); } Console.ReadKey();
还可以直接使用迭代器:
使用迭代器是另一种完全实现上面两个接口的方案,这是最为简便和可读的方法
而且使用迭代器可以很方便和快捷的设置各种枚举情况如双重的迭代反向的迭代临时的集合和负责迭代等等比上面的实现更为简单
迭代的关键字是yield需要依靠一个迭代器块(注意是循环+yield return,或者yiledbreak)
publicclassMyCollection:IEnumerable { privateobject[]array; publicMyCollection(object[]items) { array=items; } publicIEnumeratorGetEnumerator()//实现都可以依靠编译器去完成 { //foreach(objectvinarray) //{ //yieldreturnv; //} //关键字是yield并不是foreach我们也可以按照下面这个方法进行实现 for(inti=0;i可以自己设置迭代的情况:
publicclassMyCollection2:IEnumerable { privateobject[]array; publicMyCollection2(object[]items) { array=items; } //可以在迭代器块中设置迭代的实现情况即具体迭代多少个元素 //比如我们只想迭代4个元素 publicIEnumeratorGetEnumerator() { intcount=0;//设计一个标记位 foreach(objectiteminarray) { ++count; yieldreturnitem; if(count==4)yieldbreak;//break关键字退出迭代实际上迭代在实现当中就是一个循环利用break跳出也合情合理 } } } ////// MyCollection2collection2=newMyCollection2(newobject[]{4,5,6,7,8}); //它就只会输出4,5,6,7 foreach(varvincollection2) { Console.WriteLine(v); }双重迭代:
//////下面演示双重迭代即一次可以迭代两个集合中的元素 /// publicclassMyColletion3:IEnumerable { privateobject[]List1; publicstring[]List2; publicMyColletion3(object[]items1,string[]items2) { this.List1=items1; this.List2=items2; } //下面进行双重迭代 publicIEnumeratorGetEnumerator() { //关键代码 for(intindex=0;index<(List1.Length>List2.Length?List2.Length:List1.Length);index++) { yieldreturnList1[index]; yieldreturnList2[index]; } } } //////// MyColletion3collection3=newMyColletion3(newobject[]{1,2,3,5.5},newstring[]{"RanRan","Chengdu","四川"}); foreach(varvincollection3) { Console.WriteLine(v); } //迭代结果是1RanRan2Chengdu3四川反向迭代:依靠Reverse属性
//////下面演示反向迭代说白了就是迭代是从后面开始的反向迭代器是在Reverse属性当中实现的 /// publicclassMyColletion4:IEnumerable { privateobject[]items; publicMyColletion4(object[]temps) { this.items=temps; } //一般的正向迭代 publicIEnumeratorGetEnumerator() { for(intindex=0;index-1;index--) { yieldreturnitems[index]; } } } } //// MyColletion4collection4=newMyColletion4(newobject[]{1,2,3,4}); foreach(varvincollection4) { Console.WriteLine(v); } //反向迭代 foreach(varvincollection4.Reverse) { Console.WriteLine(v); } //迭代结果是4321 当然也有一个临时集合,顺便补充一下,迭代和枚举实现的方案很多,一个返回IEnumerable的方法中加上迭代器块也是一个迭代集合
具体看下面的代码
//还有一种最为简单的迭代就是一个返回IEnumerable对象的方法在这方法中写上迭代器 //在此补充一个临时集合关键看代码怎么写(以枚举当前月份的日期为列子) publicstaticIEnumerableGetMonthDate() { DateTimedt=DateTime.Now; intcurrentMonth=dt.Month; while(currentMonth==dt.Month) { stringtemp=currentMonth.ToString()+"/"+dt.Day.ToString(); dt=dt.AddDays(1); yieldreturntemp; } } ///实现 foreach(varvinGetMonthDate()) { Console.WriteLine(v); }这儿我作为一个新手自己给自己总结一下可枚举类型和接口的含义:
可枚举类型(集合&数组等):
在实际开发当中,可以自己去定义一些与集合差不多的类型,对该类型的元素的访问,用一般的while,for循环比较不方便,我们需要自己去定义一个枚举器。
枚举类型(继承IEnumerable接口):包括一个集合元素和一个枚举器。
枚举器是枚举类型当中的一个嵌套类(继承了IEnumerator接口):具体实现见上。
///////这样便可以让自定义的可枚举类型实现foreach迭代。
当然也可以直接利用迭代来实现上面两个接口。//////
接口:是一种标准,它给出了一种约束和引导,需要我们去写代码实现它。虽然看上去多次一举,不过在后面对类的实例的使用中非常方便。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。