C#中的枚举器和迭代器

Posted 康凯哇咔咔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#中的枚举器和迭代器相关的知识,希望对你有一定的参考价值。

目录

一、可枚举类型和枚举器

1. 枚举器

2. 可枚举类

3. 使用 IEnumerable 和 IEnumerator 案例

4. 泛型枚举接口

二、迭代器

1. 使用迭代器创建枚举器

2. 使用迭代器创建可枚举类

3. 常见的迭代器模式

4. 产生多个枚举类型

 5. 将迭代器作为属性

6. 迭代器的实质


一、可枚举类型和枚举器

数组为什么可以被foreach语句处理?原因是数组按需提供了一个枚举器对象。

  1. foreach结构设计用来和可枚举类型一起使用。
  2. 可枚举类型可以通过GetEnumerator方法获取对象的枚举器。
  3. 从枚举器中请求每一项并且把它作为迭代变量,该变量是只读的。

1. 枚举器

枚举器需要实现IEnumerator接口,其中包含三个函数成员:Current、MoveNext、Reset。

因为数组是可枚举类型,所以我们可以通过GetEnumerator方法获取其枚举器对象,来模拟foreach遍历,通过模拟我们可以初步认识枚举器接口中的这三个方法。

    internal class Program 
    
        static void Main(string[] args)
        
            int[] ints =  5, 6, 7 ;

            IEnumerator enumerator = ints.GetEnumerator();//获取数组的枚举器对象

            while (enumerator.MoveNext()) //MoveNext方法移动指向数组中下标的指针位置,并判断是否在数组长度范围内,返回一个bool值
            
                int current = (int)enumerator.Current;//获取当前指针指向的元素
                Console.WriteLine(current);
            

            bool beforeReset = enumerator.MoveNext();
            Console.WriteLine("位置重置为原始位置之前,enumerator.MoveNext()方法返回:0", beforeReset);

            enumerator.Reset();//将位置重置为原始状态

            //当位置没有重置时,enumerator.MoveNext()返回fasle,所以不会进入这个while循环,无法遍历
            while (enumerator.MoveNext()) 
            
                int current = (int)enumerator.Current;
                Console.WriteLine(current);
            
        
    

2. 可枚举类

前面说过数组是可枚举类型,其原因在于数组实现了IEnumerable接口。可枚举类就是指实现了IEnumerable接口的类。 

综上,可枚举类型是实现了IEnumerable接口的类,实现了IEnumerable接口中的GetEnumerator方法返回一个枚举器,枚举器是实现了 IEnumerator接口的类,通过实现IEnumerator接口三个方法来访问元素。理解图如下:

3. 使用 IEnumerable 和 IEnumerator 案例

    //枚举器
    class ColorEnumerator : IEnumerator
    
        string[] _colors;
        int _position = -1;

        public ColorEnumerator(string[] colors) 
        
            _colors = new string[colors.Length];
            Array.Copy(colors,_colors,colors.Length);
        

        public object Current 
        
            get
            
                if (_position == -1)
                    throw new InvalidOperationException();
                if (_position >= _colors.Length)
                    throw new InvalidOperationException();

                return _colors[_position];
            
        

        public bool MoveNext()
        
            if (_position < _colors.Length - 1)
            
                _position++;
                return true;
            
            else
                return false;            
        

        public void Reset()
        
            _position = -1;
        
    

    //可枚举类型
    class Spectrum : IEnumerable
    
        string[] colors =  "赤", "橙", "黄", "绿", "青", "蓝", "紫" ;

        public IEnumerator GetEnumerator()
        
            return new ColorEnumerator(colors);
        
    

    //测试
    internal class Program 
    
        static void Main(string[] args)
        
            Spectrum colors = new Spectrum();

            foreach (string color in colors) 
                Console.WriteLine(color);

        
    

4. 泛型枚举接口

  1. 泛型枚举接口的使用和非泛型枚举接口的使用是差不多的
  2. 非泛型枚举接口的实现不是类型安全的,它们返回object类型的引用,必须强转位实际类型
  3. 泛型枚举接口的实现是类型安全的,它们返回的是实际类型的对象,而非object基类的引用
  4. 应该尽量使用泛型枚举接口

二、迭代器

  1. 迭代器可以代替我们手动编码的可枚举类和枚举器。
  2. 迭代器块是有一个或多个yield语句的代码块。
  3. 迭代器块描述了希望编译器为我们创建的枚举器类的行为。
  4. 迭代器块可以是方法主体、访问器主体或运算符主体。
  5. 迭代器块中有两个特殊语句:

                i. yield return语句指定了序列中返回的下一项

                ii.yield break语句指定在序列中没有其他项

               

1. 使用迭代器创建枚举器

代码示例:

    class MyClass 
    
        public IEnumerator<string> GetEnumerator() //返回枚举器
        
            return BlackAndWhite();
        

        public IEnumerator<string> BlackAndWhite() //迭代器块
        
            yield return "赤";
            yield return "橙";
            yield return "黄";
            yield return "绿";
            yield return "青";
            yield return "蓝";
            yield return "紫";
        
    
        
    //测试
    internal class MyTest
    
        static void Main(string[] args)
        
            MyClass myClass = new MyClass();

            foreach (string element in myClass)
                Console.WriteLine(element);
           

        
    

代码图解:

2. 使用迭代器创建可枚举类

代码示例:

    class MyClass 
    
        public IEnumerator<string> GetEnumerator() //返回枚举器
        
            return BlackAndWhite().GetEnumerator();//返回从可枚举类获取的枚举器
        

        public IEnumerable<string> BlackAndWhite() //返回可枚举类
        
            yield return "赤";
            yield return "橙";
            yield return "黄";
            yield return "绿";
            yield return "青";
            yield return "蓝";
            yield return "紫";

        
    
        
    //测试
    internal class MyTest
    
        static void Main(string[] args)
        
            MyClass myClass = new MyClass();

            foreach (string element in myClass)//让类本身可枚举
                Console.WriteLine(element);

            Console.WriteLine("--------");

            foreach (string element in myClass.BlackAndWhite())//调用返回可枚举类的方法
                Console.WriteLine(element);

        
    

代码图解:

3. 常见的迭代器模式

通过前面的代码案例,创建迭代器可以用来产生可枚举类型和枚举器。总结如下:

  1. 如果我们创建返回枚举器的迭代器时,必须实现GetEnumerator方法来让类可枚举。
  2. 如果我们创建返回可枚举类型的迭代器时,我们有两种选择:

                选择1:实现GetEnumerator让类本身可枚举

                选择2:不实现GetEnumerator让类本身不可枚举,但仍可使用由迭代器产生的可枚举类

4. 产生多个枚举类型

可以在同一个类中创建多个迭代器来产生多个枚举类型。

    //注意:该类中没有实现GetEnumerator方法,所以该类本身不可以被枚举,但可以通过迭代器返回的枚举类型进行遍历
    class MyClass 
    
        string[] colors =  "赤", "橙", "黄", "绿", "青", "蓝", "紫" ;

        public IEnumerable<string> PrintOut() //迭代器返回可枚举类型
        
            for (int i = 0; i < colors.Length; i++)
                yield return colors[i];
        

        public IEnumerable<string> ReversePrintOut() //迭代器返回可枚举类型
        
            for(int i = colors.Length-1; i >= 0; i--)
                yield return colors[i];
        
    
        
    //测试
    internal class MyTest
    
        static void Main(string[] args)
        
            MyClass myClass = new MyClass();

            foreach (string color in myClass.PrintOut())
                Console.WriteLine(color);

            Console.WriteLine("--------");

            foreach (string color in myClass.ReversePrintOut())
                Console.WriteLine(color);

        
    

 5. 将迭代器作为属性

可以将迭代器作为属性。代码示例如下:

    class Colors 
    
        bool chooseEnumerator;

        string[] colors =  "赤", "橙", "黄", "绿", "青", "蓝", "紫" ;

        public Colors(bool b) 
        
            chooseEnumerator = b;
        

        //根据创建类对象时传入的布尔值控制返回不同的枚举器
        public IEnumerator<string> GetEnumerator() 
        
            return chooseEnumerator ? PrintOut : ReversePrintOut;
        

        public IEnumerator<string> PrintOut //迭代器放置在属性get访问器中
        
            get 
            
                for (int i = 0; i < colors.Length; i++)
                    yield return colors[i];
            
        

        public IEnumerator<string> ReversePrintOut //迭代器放置在属性get访问器中
        
            get 
            
                for (int i = colors.Length - 1; i >= 0; i--)
                    yield return colors[i];

            
        
    
        
    //测试
    internal class MyTest
    
        static void Main(string[] args)
        
            Colors colors = new Colors(true);
            foreach (string color in colors)
                Console.WriteLine(color);

            Console.WriteLine("--------");

            Colors reColors = new Colors(false);
            foreach (string color in reColors)
                Console.WriteLine(color);
        
    

6. 迭代器的实质

  1. 迭代器需要using指令引入System.Collections.Generic的命名空间。
  2. 在编译器生成的枚举器中,Reset方法没有实现,调用会抛异常。
  • 由编译器生成的枚举器是包含四个状态的状态机。
  • Before:首次调用MoveNext的初始状态。
  • Running:调用MoveNext后进入这个状态。在这个状态中,枚举器检查并设置下一项的位置,在遇到yield return、yield break或在迭代器体结束时,退出状态。
  • Suspended:状态机等待下次调用MoveNext的状态。
  • After:没有更多项可以枚举。

 

(注:本章学习总结自《C#图解教程》)

python -- 迭代器和装饰器

迭代器和装饰器在python中的使用十分常见,下面是个人对迭代器和装饰器的理解

迭代器

  1、迭代器 iter 的特点:

    (1).访问者不需要关心迭代器的内部结构,仅需要通过__next__()方法不断去取下一个内容
    (2).不能随机访问集合(不是set,只是一些元素的聚集体)中的某个值,只能从头到尾依次访问
    (3).访问到一半时不能后退(过去的就过去了,不能回头)
    (4).便于循环比较大的数据集合,节省内存(每次需要了指定数据时,才把该读取到内存中,eg:迭代文件时,内存中每一时刻都只有文件的一行)

1 a = iter([root,admin,python,ruby ])    #生成一个迭代器
2 print(a.__next__())   
3 print(a.__next__())      #只有一个__next__() 方法
4 print(waitting!)    #在第二代的过程中,能停下来做其他的事
5 print(a.__next__())     
6 print(a.__next__())
7 #print(a.__next__())    #报错 超出迭代器范围

  常见迭代器使用:
    读文件时:
      with open(‘a*.txt‘,‘r+‘) as f:
        for line in f:
        ...
    这里的for i in f:就是用的迭代器,避免依次把文件内容全部读取到内存中,节省内存。

 

在介绍迭代器时,还有个概念要明白,何为生成器

  生成器 generator
  定义: 一个函数调用时返回一个迭代器,那么这个函数就叫做生成器,如果函数中包含了yield语法,那么这个函数就会变成一个生成器(保存函数的中断状态)

 1 >>> 
 2 >>> def test_yeild(n):
 3     while n > 0:
 4         n -= 2
 5         yield 10
 6         print(注意何时打印这句话...)
 7 
 8         
 9 >>> t = test_yeild(12)      # 与普通函数不同,这里只是生成一个迭代器,不会跳到‘函数体’里面去(执行函数体),而是等待迭代器的__next__()方法
10 >>> t.__next__()
11 10                # 返回yield后面的值,并在执行完yield后 暂停程序 ,等待下一次__next__(),然后接着从暂停的地方开始运行
12 >>> t.__next__()          
13 注意何时打印这句话...
14 10
15 >>> t.__next__()
16 注意何时打印这句话...
17 10
18 >>> t.__next__()
19 注意何时打印这句话...
20 10
21 >>> t.__next__()
22 注意何时打印这句话...
23 10
24 >>> t.__next__()
25 注意何时打印这句话...
26 10
27 >>> t.__next__()
28 注意何时打印这句话...
29 Traceback (most recent call last):
30   File "<pyshell#162>", line 1, in <module>
31     t.__next__()
32 StopIteration
33 >>> 

yield 可以返回一个值,也可以接收一个值,给yield传值用send()

看下面例子,用yield做了个简单的异步操作:

 1 import time
 2 
 3 def consumer(name):
 4     print("%s 准备吃包子啦!" %name)
 5     while 1:
 6         baozi = yield
 7         print("包子[%s]来了,被[%s]吃了" %(baozi,name))
 8         
 9 def producer(name):
10     c = consumer(A)
11     c2 = consumer(B)
12     c.__next__()
13     c2.__next__()
14     print("%s开始准备做包子啦!" %name)
15     for i in range(3):
16         time.sleep(1)        #执行时为了更清楚的看到过程
17         print("做了2个包子")
18         c.send(i)
19         c2.send(i)
20               
22 producer(root)

A 准备吃包子啦!
B 准备吃包子啦!
root开始准备做包子啦!
做了2个包子
包子[0]来了,被[A]吃了
包子[0]来了,被[B]吃了
做了2个包子
包子[1]来了,被[A]吃了
包子[1]来了,被[B]吃了
做了2个包子
包子[2]来了,被[A]吃了
包子[2]来了,被[B]吃了

  

 1 def account(money):
 2     while money > 0:
 3         out_money = yield 
 4         money -= out_money
 5         #yield 300
 6         print(又来取钱了!取了%s %out_money)
 7         
 8 atm = account(1500)
 9 atm.__next__()
10 for i in range(5):
11     atm.send(300)
12     #在第一次用__next__()方法启动迭代器后,每次调用迭代器,就会自动执行__next__()方法
13     

 

装饰器

  装饰器基本功能:对已有函数进行扩展

  (个人初步理解,可能错误)
  装饰器执行过程:
    1.在调用被装饰器装饰的函数时,先把被装饰器装饰的函数(装饰器下一行代码中函数)的函数名传递给装饰器从内到外第二层定义的函数
    2.把被装饰器装饰的函数的实参传递给装饰器最内层函数
    3.在装饰器内部实现被装饰器装饰的函数的调用

 简单装饰器:

 1 def login(func):
 2     def inner(*args,**kwargs):
 3         print("我是做身份验证的,验证成功才能调用函数")
 4         print(args,kwargs)
 5         func(*args,**kwargs)
 6         #return func(*args,**kwargs)
 7     return inner
 9     #若传进来的是函数名,那么没有执行‘()‘的话,即是函数的内存地址
10 
11 #@login    #(装饰器)程序一执行,就会调用装饰器的函数
12 def home(name):
13     print("welcome [%s] vidited the home page" %name)
14 
15 @login   #@符号的作用: 相当于执行 tv = login(tv) 
16 #def tv(name,passwd=123):
17 def tv(*args,**kwargs):
18     print("welcome [%s] vidited the tv page" %args )
19     #若这里有返回值,则仅需要在inner里面返回函数结果
20 #@login
21 def movices(name):
22     print("welcome [%s] vidited the movices page" %name)
23     
24 tv(root)
25 tv(xtsec,passwd=123)

 

复杂装饰器:

 1 ‘‘‘
 2 调用装饰器时(eg:@login(f1,f2)),传递多个参数的装饰器(三层装饰器)
 3 ‘‘‘
 4 def before():
 5     print("执行要装饰的函数之前执行")
 6 
 7 def affter():
 8     print("执行要装饰的函数之后执行")      #没有return的函数默认返回None
 9 
10 def test_ge(before1,affter1):
11     def login(func):
12         def inner(*args,**kwargs):
13             result_before = before1()
14             if(result_before != None):     
15                 return 执行before条件未成功!‘    #这里运行就表示before函数没有正确运行结束,所以表示验证没通过
16             ret = func(*args,**kwargs)
17             if(ret != None):
18                 return 执行被修饰函数不成功!‘    #若前面验证通过,且装饰器正确执行,到这里整个装饰器运行结束
19             result_afftr = affter1()
20             if(result_afftr != None):
21                 return 执行时不满足要求!‘     #若前面装饰器修身的函数没正确运行,就会到这里,常用来返回错误信息
22             return ret
23         return inner
24     return login
25 
26 def home(name):
27     print("welcome [%s] vidited the home page" %name)
28 
29 ‘‘‘
30 接下来这一行有三个操作:
31 1.调用函数 test_ge(before, affter)
32 [email protected]   login为调用test_ge(before, affter)的返回值
33 3.新tv = inner    login()的返回值
34 ‘‘‘
35 @test_ge(before, affter)   
36 def tv(*args,**kwargs):
37     print("welcome [%s] vidited the tv page" %args )
38     return 1
39 
40 def movices(name):
41     print("welcome [%s] vidited the movices page" %name)
42 
43 
44 tv(root,passwd=admin)   
45     #在这里执行tv()时,事实上时执行新的tv(),即是inner()
46 a = before()
47 print(type(a),a)
48 if a == None:
49     print(110)

在日常使用中,一般就用普通的装饰器,复杂度这个最好明白运行原理即过程

 
















以上是关于C#中的枚举器和迭代器的主要内容,如果未能解决你的问题,请参考以下文章

C# 枚举器和可枚举类型

枚举器和迭代器

枚举器和迭代器

设计模式 - 适配器模式(adapter pattern) 枚举器和迭代器 具体解释

广度优先搜索中的c ++迭代器和指针使用

C# 学习之路(十八)