C#中可枚举类型详解
枚举是迭代一个集合中的数据项的过程。
我们经常使用的大多数集合实际上都已经实现了枚举的接口IEnumerable和IEnumerator接口,这样才能使用foreach迭代,有些是含有某种抽象了枚举细节的接口:ArrayList类型有索引,BitArray有Get方法,哈希表和字典有键和值..........其实他们都已经实现了IEnumerable和IEnumerator接口。所以一切的集合和数组都可以用IEnumerable或者IEnumerable<T>接口来定义。
IEnumerable lists1 = new int[] { 3, 4, 5 }; foreach(var val in lists1) { Console.WriteLine(val); } IEnumerable<int> lists2=new int[]{1,2,3}; foreach(var val in lists2) { Console.WriteLine(val); }
下面讲解一下 自己来定义可枚举类型(简单说就是自己定义的 ,可以进行foreach迭代的集合):
因为枚举非常有好处,可以消除很多的错误,所以实现某种标准是有好处的。这种标准就是IEnumerable和IEnumerator接口,必须实现了它才能够使用foreach迭代,才能真正算是一个自己定义的,功能健全的集合。
我们自己建立的可枚举类型必须实现IEnumerable和IEnumerator接口(其实两者都有一个泛型实现)。
IEnumerable接口含有一个方法,该方法返回一个枚举器对象,枚举器对象实现了IEnumerator接口(实际上可以认为继承和实现了IEnumerator的接口的类的对象就是枚举器对象),可以用它来进行迭代。
下面是两个接口的定义(系统早已经定义好):
public interface IEnumerable { IEnumerator GetEnumerator(); }
该接口只有一个GetEnumerator的方法,返回一个枚举器,用于枚举集合中的元素。
public interface IEnumerator { object Current { get; };//Current属性返回集合的当前元素 bool MoveNext(); //将枚举移动到下一位 void Reset(); //使枚举回到开头 }
凡是继承和实现了上面这个接口的类对象就是枚举器,可以利用上面的三个方法进行枚举,非常安全。不过需要自己在继承了接口的代码中去写实现过程。
一般的情况是:枚举器是枚举模式的一部分,通常被实现为枚举类型(继承IEnumerable)的一个嵌套类(继承IEnumerator)。嵌套类的好处就是可以访问外部类的私有成员,不破坏封装的原则。
下面我们自己来定义一个枚举类型,代码如下:
public class SimpleCollection :IEnumerable { //定义一个数组的字段 private object[] array; //定义一个构造函数 public SimpleCollection(object []items) { array = items; } //实现IEnumerable接口的GetNumerator方法 该方法返回一个继承IEnumerator接口的类的实例 public IEnumerator GetEnumerator() { return new Enumerator(array); } //定义一个嵌套类来继承IEnumerator的接口 public class Enumerator : IEnumerator { //定义一个标记字段 private int flag; //定义一个数组的字段 private object[] elements = null; //定义一个构造函数 public Enumerator(object []items) { elements = items; flag = -1; //将标记位初始化 //也可以采用下面的方法 //elements = new object[items.Length]; //Array.Copy(items, elements, items.Length);//此静态方法用于将一个数组中的元素复制到另外一个数组 } //实现IEnumerator接口的Current属性; 此属性返回集合的当前元素,是只读的 public object Current { get { if (flag > elements.Length - 1) throw new InvalidOperationException("枚举已经结束"); else if (flag < 0) throw new InvalidOperationException("枚举尚未开始"); else return elements[flag]; } } //实现IEnumerator接口的MoveNext方法 将枚举移动到下一位 public bool MoveNext() { ++flag; if (flag > (elements.Length - 1)) return false; else return true; } //实现IEnumerator接口的Reset方法 使枚举回到开头 public void Reset() { flag = -1; } }
下面来延时如何使用枚举类型:
//下面来看枚举类型的使用 SimpleCollection collection = new SimpleCollection(new object[]{1,2,3,4,5}); //使用方法 //接口 变量名=继承了该接口的类的实例 IEnumerator enumrator = collection.GetEnumerator(); while(enumrator.MoveNext()) { Console.WriteLine(enumrator.Current); } Console.ReadKey();
SimpleCollection simple = new SimpleCollection(new object[] { 1, 2, 3, 4, 5, 6 }); IEnumerator enumerator = simple.GetEnumerator(); while(enumerator.MoveNext()) { Console.WriteLine(enumerator.Current); } //最重要的是,实现了那两个接口,我们就可以对我们的集合使用foreach迭代了,看下面 foreach(var s in simple) { Console.WriteLine(s); }
下面给出两个接口的泛型实现:
首先需要注意的是:
1.IEnumerable<T>接口继承自IEnumerable 两者具有相同接口,所以必须实现泛型和非泛型版本的GetEumerator方法
2.IEnumerator<T>接口继承自IEnumerator和IDisposable 需要多实现泛型和非泛型版本的Current属性和IDisposable接口的Dispose方法。
代码如下:
////下面创建一个可枚举的泛类型 //首先该类型必须要继承IEnumerable<T>接口 //因为IEnumerable<T>接口继承IEnumerable接口 所以必须同时实现泛型和非泛型的GetEnumerator方法 public class SimpleCollection<T> : IEnumerable<T> { private T[] array; public SimpleCollection(T[] items) { array = items; } //实现IEnumerable<T>接口的GetNumerator方法 该方法返回一个继承IEnumerator接口的类的实例 public IEnumerator<T> GetEnumerator() { return new Enumerator<T>(array);//这步需要重视 } //为了避免混淆 在此显式实现非泛型的接口 IEnumerator IEnumerable.GetEnumerator() { return new Enumerator<T>(array);//这步需要重视 } //定义一个嵌套类来继承IEnumerator<T>的接口 //IEnumerator<T>接口继承自IDisposable和IEnumerator接口 //该接口的唯一成员是Current属性 但是同时也要实现其非泛型版本!!! //另外还需要实现IDisposable的Dispose方法和IEnumerator的两个方法 public class Enumerator<_T> : IEnumerator<_T> { private int flag; private _T[] elements = null; public Enumerator(_T[] items) { elements = items; flag = -1; } //实现IEnumerator<T>接口的Current属性; 此属性返回集合的当前元素,是只读的 public _T Current { get { if (flag > elements.Length - 1) throw new InvalidOperationException("枚举已经结束"); else if (flag < 0) throw new InvalidOperationException("枚举尚未开始"); else return elements[flag]; } } //为了避免混淆 显示实现IEnumerator接口的Current属性 object IEnumerator.Current { get { return Current; } //直接返回上面的泛型属性 比较经典 } //实现IDisposable接口的Dispose方法 支持确定性垃圾回收 将枚举数的状态设置为after 也就是把标记位设为最大索引+1 public void Dispose() { flag = elements.Length + 1; } //实现IEnumerator接口的MoveNext方法 将枚举移动到下一位 public bool MoveNext() { ++flag; if (flag > (elements.Length - 1)) return false; else return true; } //实现IEnumerator接口的Reset方法 使枚举回到开头 public void Reset() { flag = -1; } }
怎么使用呢:
SimpleCollection<string> colletion = new SimpleCollection<string>(new string[] { "ranran", "Huaran" }); IEnumerator<string> enumorator = colletion.GetEnumerator(); while(enumorator.MoveNext()) { Console.WriteLine(enumorator.Current); } foreach(var v in colletion) { Console.WriteLine(v); } Console.ReadKey();
还可以直接使用迭代器:
使用迭代器是另一种完全实现上面两个接口的方案,这是最为简便和可读的方法
而且使用迭代器可以很方便和快捷的设置各种枚举情况 如双重的迭代 反向的迭代 临时的集合和负责迭代等等 比上面的实现更为简单
迭代的关键字是yield 需要依靠一个迭代器块(注意是循环+yield return,或者 yiled break)
public class MyCollection:IEnumerable { private object[] array; public MyCollection(object []items) { array = items; } public IEnumerator GetEnumerator() //实现都可以依靠编译器去完成 { //foreach (object v in array) //{ // yield return v; //} //关键字是yield 并不是foreach 我们也可以按照下面这个方法进行实现 for(int i=0;i<array.Length;i++) { yield return array[i]; } //当然其它的while循环也可以。。 } } //实现: MyCollection collection = new MyCollection(new object[] { 1, 2, 3 }); foreach(var v in collection) { Console.WriteLine(v); }
可以自己设置迭代的情况:
public class MyCollection2:IEnumerable { private object[] array; public MyCollection2(object []items) { array = items; } //可以在迭代器块中设置迭代的实现情况 即具体迭代多少个元素 //比如我们只想迭代4个元素 public IEnumerator GetEnumerator() { int count = 0;//设计一个标记位 foreach(object item in array) { ++count; yield return item; if (count == 4) yield break; //break关键字 退出迭代 实际上迭代在实现当中就是一个循环 利用break跳出也合情合理 } } } ////// MyCollection2 collection2 = new MyCollection2(new object[]{4,5,6,7,8}); //它就只会输出4,5,6,7 foreach (var v in collection2) { Console.WriteLine(v); }
双重迭代:
/// <summary> /// 下面演示双重迭代 即一次可以迭代两个集合中的元素 /// </summary> public class MyColletion3:IEnumerable { private object[] List1; public string[] List2; public MyColletion3(object []items1,string []items2) { this.List1 = items1; this.List2 = items2; } //下面进行双重迭代 public IEnumerator GetEnumerator() { //关键代码 for(int index=0;index<(List1.Length>List2.Length?List2.Length:List1.Length);index++) { yield return List1[index]; yield return List2[index]; } } } //////// MyColletion3 collection3 = new MyColletion3(new object[] { 1, 2, 3, 5.5 }, new string[] { "RanRan", "Chengdu", "四川" }); foreach(var v in collection3) { Console.WriteLine(v); } //迭代结果是1 RanRan 2 Chengdu 3 四川
反向迭代:依靠Reverse属性
/// <summary> /// 下面演示反向迭代 说白了就是迭代是从后面开始的 反向迭代器是在Reverse属性当中实现的 /// </summary> public class MyColletion4:IEnumerable { private object[] items; public MyColletion4(object []temps) { this.items = temps; } //一般的正向迭代 public IEnumerator GetEnumerator() { for(int index=0;index<items.Length;index++) { yield return items[index]; } } //实现反向迭代 public IEnumerable Reverse //注意返回IEnumerable对象 { get { for (int index = items.Length - 1; index > -1; index--) { yield return items[index]; } } } } //// MyColletion4 collection4 = new MyColletion4(new object[] { 1, 2, 3, 4 }); foreach (var v in collection4) { Console.WriteLine(v); } //反向迭代 foreach(var v in collection4.Reverse) { Console.WriteLine(v); } //迭代结果是 4 3 2 1
当然也有一个临时集合,顺便补充一下,迭代和枚举实现的方案很多,一个返回IEnumerable的方法中加上迭代器块也是一个迭代集合
具体看下面的代码
//还有一种最为简单的迭代 就是一个返回IEnumerable对象的方法 在这方法中写上迭代器 //在此补充一个临时集合 关键看代码怎么写(以枚举当前月份的日期为列子) public static IEnumerable GetMonthDate() { DateTime dt = DateTime.Now; int currentMonth = dt.Month; while(currentMonth==dt.Month) { string temp = currentMonth.ToString() + "/" + dt.Day.ToString(); dt = dt.AddDays(1); yield return temp; } } ///实现 foreach(var v in GetMonthDate()) { Console.WriteLine(v); }
这儿 我作为一个新手自己给自己总结一下可枚举类型和接口的含义:
可枚举类型(集合&数组等):
在实际开发当中,可以自己去定义一些与集合差不多的类型,对该类型的元素的访问,用一般的while,for循环比较不方便,我们需要自己去定义一个枚举器。
枚举类型(继承IEnumerable接口):包括一个集合元素和一个枚举器。
枚举器是枚举类型当中的一个嵌套类(继承了IEnumerator接口):具体实现见上。
/////// 这样便可以让自定义的可枚举类型实现foreach迭代。
当然也可以直接利用迭代来实现上面两个接口。//////
接口:是一种标准,它给出了一种约束和引导,需要我们去写代码实现它。虽然看上去多次一举,不过在后面对类的实例的使用中非常方便。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持猪先飞。
相关文章
- 我们在使用C#做项目的时候,基本上都需要制作登录界面,那么今天我们就来一步步看看,如果简单的实现登录界面呢,本文给出2个例子,由简入难,希望大家能够喜欢。...2020-06-25
- 这篇文章主要介绍了C# 字段和属性的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下...2020-11-03
- 这篇文章主要介绍了C#中截取字符串的的基本方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-03
- 这篇文章主要介绍了C#实现简单的Http请求的方法,以实例形式较为详细的分析了C#实现Http请求的具体方法,需要的朋友可以参考下...2020-06-25
- 本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
- 本文主要介绍了C#中new的几种用法,具有很好的参考价值,下面跟着小编一起来看下吧...2020-06-25
使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序)
这篇文章主要介绍了使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25- 这篇文章主要介绍了C#开发Windows窗体应用程序的简单操作步骤,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-12
- 这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
- 最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
- 本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
- 轻松学习C#的基础入门,了解C#最基本的知识点,C#是一种简洁的,类型安全的一种完全面向对象的开发语言,是Microsoft专门基于.NET Framework平台开发的而量身定做的高级程序设计语言,需要的朋友可以参考下...2020-06-25
- 本文主要介绍了C#变量命名规则小结,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-09
- 这篇文章主要介绍了C#绘制曲线图的方法,以完整实例形式较为详细的分析了C#进行曲线绘制的具体步骤与相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
- 本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
- 这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
- 这篇文章主要介绍了c#中(&&,||)与(&,|)的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
- 这篇文章主要用实例讲解C#递归算法的概念以及用法,文中代码非常详细,帮助大家更好的参考和学习,感兴趣的朋友可以了解下...2020-06-25
- 下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
- 这篇文章主要介绍了C#中list用法,结合实例形式分析了C#中list排序、运算、转换等常见操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25