欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C#中可枚举类型详解

程序员文章站 2023-12-17 14:50:04
枚举是迭代一个集合中的数据项的过程。 我们经常使用的大多数集合实际上都已经实现了枚举的接口ienumerable和ienumerator接口,这样才能使用foreach迭...

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

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

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

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

 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

上一篇:

下一篇: