C#中可枚举类型详解

作者:华然 时间:2023-03-24 20:39:15 

枚举是迭代一个集合中的数据项的过程。

我们经常使用的大多数集合实际上都已经实现了枚举的接口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迭代。

              当然也可以直接利用迭代来实现上面两个接口。//////

接口:是一种标准,它给出了一种约束和引导,需要我们去写代码实现它。虽然看上去多次一举,不过在后面对类的实例的使用中非常方便。

来源:http://www.cnblogs.com/Huaran1chendu/p/4838536.html

标签:C#,可枚举类型
0
投稿

猜你喜欢

  • Java数据结构之链表详解

    2023-10-20 09:14:08
  • Mybatis一对一延迟加载实现过程解析

    2022-09-07 12:45:43
  • 生产消费者模式实现方式和线程安全问题代码示例

    2023-11-26 19:44:17
  • java Spring Boot 配置redis pom文件操作

    2022-01-25 20:57:13
  • C#新手常犯的错误汇总

    2021-10-29 05:05:53
  • Java实现的基于socket通信的实例代码

    2021-12-30 19:06:50
  • java中sdk与jdk的区别详细解析

    2023-11-25 03:37:14
  • 根据灰度值填充字符-单文件单线程版

    2023-10-12 00:50:24
  • C语言实现四窗口聊天

    2021-12-28 01:02:12
  • Android实现移动小球和CircularReveal页面切换动画实例代码

    2023-03-03 03:45:50
  • C#异步编程由浅入深(二)之Async/Await的使用

    2022-10-14 16:45:20
  • C#表达式和运算符详细解析

    2021-09-30 23:39:09
  • Android简单实现菜单拖拽排序的功能

    2023-03-10 22:32:31
  • Unity常用命令模式详解

    2021-12-30 16:58:08
  • .NET的深复制方法(以C#语言为例)

    2022-07-18 00:17:05
  • Spring 实现excel及pdf导出表格示例

    2023-12-21 04:17:32
  • 使用淘宝ip地址库查ip的示例

    2023-02-05 19:19:11
  • Spring Cloud Gateway集成Sentinel流控详情

    2023-11-09 20:27:31
  • Android实现倒计时效果

    2021-10-13 03:25:29
  • SpringBoot封装响应数据实现过程详解

    2023-01-17 21:32:51
  • asp之家 软件编程 m.aspxhome.com