C# 枚举器(enumerator)

总结:

1、枚举器就像是序列中的“游标”或“书签”。可以有多个“书签”,移动其中任何一个都可以枚举集合,与其他枚举器互不影响。用来遍历数据结构(单项表链、数组、集合类成员等)。

2、可以使用foreach 遍历枚举器。foreach 用来遍历鸭子类型.点击查看foreach详细用法

什么是枚举器

实现IEnumerator接口的类就是枚举器。

枚举器作用

1、枚举器就像是序列中的“游标”或“书签”。可以有多个“书签”,移动其中任何一个都可以枚举集合,与其他枚举器互不影响。用来遍历数据结构(表链、数组、集合类成员等)。

2、以下案例数组 作为内部数据结构,后期也可以换成数组,链表,树,图等等,而使用者却不用关心这些内部数据表示,这就是迭代器的妙处所在。

存在的问题

1、对于需要递归遍历的数据结构(如二叉树),指示状态可能就会变得相当复杂。为了减少实现此模式所带来的挑战,C# 2.0引入了迭代器, 新增了 yield 上下文关键字。
2、只能顺序遍历

枚举器的原理

 

 

 

 

 

指针初始位置不在数据结构内(数组、表链、树等结构)、第一次movenext()后, 数据结构项(表链 、数组等)不为空则返回true,否则为false

 

IEnumerator<string> enumerator = mc.BlackAndWhite().GetEnumerator();
        try
        {
                   //数据结构项(表链 、数组等)不为空则返回true,否则为false

            while (enumerator.MoveNext())
            {
                                //读取当前指针位置处的值
                string shade = enumerator.Current;
                Console.Write(shade);
            }
        }
        finally
        {
            if (enumerator != null)
            {
                enumerator.Dispose();
            }
        }

 

 

IEnumerator接口

实现了IEnumerator接口的枚举器包含3个public类型的成员Current、MoveNext()以及Reset()

在 IEnumerator 嵌套类中实现,以便可以创建多个枚举器。

枚举器内部可以用数组、表链、等其他数据结构。以下案例用数组

Current:返回当前处理的元素。

  • 它是只读属性。
  • 它返回object类型的引用,所以可以返回任何类型

MoveNext():把枚举器位置向前到集合中的下一项方法

  • 它也返回布尔值,指示新的位置是有效位置还是已经超过了序列的尾部。
  • 如果新的位置是有效的
  • 如果新的位置是无效的(比如当前位置到达了尾部),方法返回false。
  • 枚举器的原始位置在序列中的第一项之前,因此MoveNext必须在第一次使用Current之前调用。

  • int[] i = {1,1,1,2 };
    var  ie= i.GetEnumerator();
    //错误的写法,原因是枚举器位于集合中第一个元素之前,紧跟在创建枚举器之后。 MoveNext 在读取的值之前,必须调用以将枚举数前移到集合的第一个元素 Current 。
    Console.Write(ie.Current);
    ie.MoveNext();

     

Reset():把位置重置为原始状态方法(Reset 方法通常会抛出 NotImplementedException,因此不得进行调用。如果需要重新开始枚举,只要新建一个枚举器即可。)

枚举器的实现

这种方式不好,不能创建多个枚举实例。

using System;
using System.Collections;
namespace ConsoleEnum
{
    public class cars : IEnumerator,IEnumerable
    {
       private car[] carlist;
       int position = -1;
       //Create internal array in constructor.
       public cars()
       {
           carlist= new car[6]
           {
               new car("Ford",1992),
               new car("Fiat",1988),
               new car("Buick",1932),
               new car("Ford",1932),
               new car("Dodge",1999),
               new car("Honda",1977)
           };
       }
       //IEnumerator and IEnumerable require these methods.
       public IEnumerator GetEnumerator()
       {
           return (IEnumerator)this;
       }
       //IEnumerator
       public bool MoveNext()
       {
           position++;
           return (position < carlist.Length);
       }
       //IEnumerable
       public void Reset()
       {
           position = -1;
       }
       //IEnumerable
       public object Current
       {
           get { return carlist[position];}
       }
    }
  }

本文中的示例尽量简单(所以采用数组而不是其他数据解构(单项表链)),以更好地解释这些接口的使用。不过该案例也反应出一个问题。

如果多线程访问方法就会造成这个实例,由于MoveNext()是共享的。就会导致乱序。

若要使代码更可靠并确保代码使用当前最佳做法准则,请修改代码,如下所示:

最佳做法

  • 将 IEnumerable和IEnumerator两个接口的功能分开。集合类本身实现IEnumerable,集合类内部嵌套枚举器 (继承IEnumerator接口的类),以便可以创建多个枚举器。
  • 枚举器就像是序列中的“游标”或“书签”。可以有多个“书签”,移动其中任何一个都可以枚举集合,与其他枚举器互不影响。
  • 为 方法提供 Current 异常处理 IEnumerator 。 如果集合的内容更改,将 reset 调用 方法。 因此,当前枚举器失效,您将收到 IndexOutOfRangeException 异常。 其他情况也可能导致此异常。 因此,实现 Try...Catch 块以捕获此异常并引发 InvalidOperationException 异常。
using System;
using System.Collections;
namespace ConsoleEnum
{
    public class cars : IEnumerable
    {
        private car[] carlist;
  
        //Create internal array in constructor.
        public cars()
        {
            carlist= new car[6]
            {
                new car("Ford",1992),
                new car("Fiat",1988),
                new car("Buick",1932),
                new car("Ford",1932),
                new car("Dodge",1999),
                new car("Honda",1977)
            };
        }
        //private enumerator class
        private class  MyEnumerator:IEnumerator
        {
            public car[] carlist;
            int position = -1;

            //constructor
            public MyEnumerator(car[] list)
            {
                carlist=list;
            }
            private IEnumerator getEnumerator()
            {
                return (IEnumerator)this;
            }
            //IEnumerator
            public bool MoveNext()
            {
                position++;
                return (position < carlist.Length);
            }
            //IEnumerator
            public void Reset()
            {
                position = -1;
            }
            //IEnumerator
            public object Current
            {
                get
                {
                    try
                    {
                        return carlist[position];
                    }
                    catch (IndexOutOfRangeException)
                    {
                        throw new InvalidOperationException();
                    }
                }
            }
        }  //end nested class
      public IEnumerator GetEnumerator()
      {
          return new MyEnumerator(carlist);
      }
    }
}

 

 

枚举器在集合中应用

首先、集合必须继承IEnumerable 接口,该接口就是告诉别人他是可以枚举的,他的内部已经实现了枚举器。别人可以通过IEnumerable 接口 提供的GetEnumerator()方法获得枚举器。然后通过枚举器的movenext访问集合成员。

第二、然后需要在集合类的内部放一个枚举器(嵌套一个现实 IEnumerator接口 的类)。然把集合自身的单向链表传入枚举器,枚举器就像放在链表上的游标。

第三、别人就可以通过获取调用集合类的GetEnumerator()方法,获取到集合类的枚举器。通过枚举器顺序的访问集合。

 

实现IEnumerable接口的类有哪些:

数组、集合类 等等

 泛型枚举接口

目前我们描述的枚举接口都是非泛型版本。实际上,在大多数情况下你应该使用泛型版本IEnumerable<T>IEnumerator<T>。它们叫做泛型是因为使用了C#泛型(参见第17章),其使用方法和非泛型形式差不多。
两者间的本质差别如下:

  • 对于非泛型接口形式:
    • IEnumerable接口的GetEnumerator方法返回实现IEnumerator枚举器类的实例
    • 实现IEnumerator的类实现了Current属性,它返回object的引用,然后我们必须把它转化为实际类型的对象
  • 对于泛型接口形式:
    • IEnumerable<T>接口的GetEnumerator方法返回实现IEnumator<T>的枚举器类的实例
    • 实现IEnumerator<T>的类实现了Current属性,它返回实际类型的对象,而不是object基类的引用

需要重点注意的是,我们目前所看到的非泛型接口的实现不是类型安全的。它们返回object类型的引用,然后必须转化为实际类型。
泛型接口的枚举器是类型安全的,它返回实际类型的引用。如果要创建自己的可枚举类,应该实现这些泛型接口。非泛型版本可用于C#2.0以前没有泛型的遗留代码。
尽管泛型版本和非泛型版本一样简单易用,但其结构略显复杂。

 

posted @ 2022-01-30 14:43  小林野夫  阅读(3600)  评论(0编辑  收藏  举报
原文链接:https://www.cnblogs.com/cdaniu/