.net中的迭代器实现及原理

1.迭代一个方法。C#代码如下 :

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Collections;
 4 
 5 namespace VS2010.ConsoleApp.Iterator
 6 {
 7     class Program
 8     {
 9         private static IEnumerable IteratorMethod()
10         {
11             yield return "1";
12             yield return 2;
13             yield return true;
14             yield return 4;
15         }
16         private static IEnumerable<int> IteratorMethodGeneric()
17         {
18             yield return 1;
19             yield return 2;
20             yield return 3;
21             yield return 4;
22         }
23         static void Main(string[] args)
24         {
25             foreach (var s in IteratorMethod())
26             {
27                 Console.WriteLine(s);
28             }
29             foreach (var s in IteratorMethodGeneric())
30             {
31                 Console.WriteLine(s);
32             }
33         }
34     }
35 }

编译后的Main函数的IL代码如下:

 1 .method private hidebysig static void  Main(string[] args) cil managed
 2 {
 3   .entrypoint
 4   // 代码大小       135 (0x87)
 5   .maxstack  2
 6   .locals init ([0] object s,
 7            [1] int32 V_1,
 8            [2] class [mscorlib]System.Collections.IEnumerator CS$5$0000,
 9            [3] bool CS$4$0001,
10            [4] class [mscorlib]System.IDisposable CS$0$0002,
11            [5] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> CS$5$0003)
12   IL_0000:  nop
13   IL_0001:  nop
14   IL_0002:  call       class [mscorlib]System.Collections.IEnumerable VS2010.ConsoleApp.Iterator.Program::IteratorMethod()
15   IL_0007:  callvirt   instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.IEnumerable::GetEnumerator()
16   IL_000c:  stloc.2
17   .try
18   {
19     IL_000d:  br.s       IL_001f
20     IL_000f:  ldloc.2
21     IL_0010:  callvirt   instance object [mscorlib]System.Collections.IEnumerator::get_Current()
22     IL_0015:  stloc.0
23     IL_0016:  nop
24     IL_0017:  ldloc.0
25     IL_0018:  call       void [mscorlib]System.Console::WriteLine(object)
26     IL_001d:  nop
27     IL_001e:  nop
28     IL_001f:  ldloc.2
29     IL_0020:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
30     IL_0025:  stloc.3
31     IL_0026:  ldloc.3
32     IL_0027:  brtrue.s   IL_000f
33     IL_0029:  leave.s    IL_0045
34   }  // end .try
35   finally
36   {
37     IL_002b:  ldloc.2
38     IL_002c:  isinst     [mscorlib]System.IDisposable
39     IL_0031:  stloc.s    CS$0$0002
40     IL_0033:  ldloc.s    CS$0$0002
41     IL_0035:  ldnull
42     IL_0036:  ceq
43     IL_0038:  stloc.3
44     IL_0039:  ldloc.3
45     IL_003a:  brtrue.s   IL_0044
46     IL_003c:  ldloc.s    CS$0$0002
47     IL_003e:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
48     IL_0043:  nop
49     IL_0044:  endfinally
50   }  // end handler
51   IL_0045:  nop
52   IL_0046:  nop
53   IL_0047:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> VS2010.ConsoleApp.Iterator.Program::IteratorMethodGeneric()
54   IL_004c:  callvirt   instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
55   IL_0051:  stloc.s    CS$5$0003
56   .try
57   {
58     IL_0053:  br.s       IL_0066
59     IL_0055:  ldloc.s    CS$5$0003
60     IL_0057:  callvirt   instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
61     IL_005c:  stloc.1
62     IL_005d:  nop
63     IL_005e:  ldloc.1
64     IL_005f:  call       void [mscorlib]System.Console::WriteLine(int32)
65     IL_0064:  nop
66     IL_0065:  nop
67     IL_0066:  ldloc.s    CS$5$0003
68     IL_0068:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
69     IL_006d:  stloc.3
70     IL_006e:  ldloc.3
71     IL_006f:  brtrue.s   IL_0055
72     IL_0071:  leave.s    IL_0085
73   }  // end .try
74   finally
75   {
76     IL_0073:  ldloc.s    CS$5$0003
77     IL_0075:  ldnull
78     IL_0076:  ceq
79     IL_0078:  stloc.3
80     IL_0079:  ldloc.3
81     IL_007a:  brtrue.s   IL_0084
82     IL_007c:  ldloc.s    CS$5$0003
83     IL_007e:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
84     IL_0083:  nop
85     IL_0084:  endfinally
86   }  // end handler
87   IL_0085:  nop
88   IL_0086:  ret
89 } // end of method Program::Main
View Code

从Main的IL代码中可以看出第一个循环迭代时使用了IEnumerable的GetEnumerator方法,返回一个IEnumerator接口,而该接口返回的就是object类型, 所以这个迭代方法中返回的类型可以是任意类型;第二个循环迭代使用泛型IEnumerable<Int>的GetEnumerator<Int>方法,返回一个泛型IEnumerator<Int>接口,那么这个方法中只能返回Int类型的数据。

看下IteratorMethod()方法的IL代码,那个泛型方法的代码是类似的:

 1 .method private hidebysig static class [mscorlib]System.Collections.IEnumerable 
 2         IteratorMethod() cil managed
 3 {
 4   // 代码大小       14 (0xe)
 5   .maxstack  2
 6   .locals init ([0] class VS2010.ConsoleApp.Iterator.Program/'<IteratorMethod>d__0' V_0,
 7            [1] class [mscorlib]System.Collections.IEnumerable V_1)
 8   IL_0000:  ldc.i4.s   -2
 9   IL_0002:  newobj     instance void VS2010.ConsoleApp.Iterator.Program/'<IteratorMethod>d__0'::.ctor(int32)
10   IL_0007:  stloc.0
11   IL_0008:  ldloc.0
12   IL_0009:  stloc.1
13   IL_000a:  br.s       IL_000c
14   IL_000c:  ldloc.1
15   IL_000d:  ret
16 } // end of method Program::IteratorMethod
View Code

IL代码中的返回类<IteratorMethod>d__0的了一个实例对象,这个类的定义代码如下图:

这个类实现了IEnumerable,IEnumerator及其泛型接接口,这个类就是一个迭代器类,原理和迭代器模式是一样的,它将我在方法中需要迭代的数据封装在内部通过接口IEnumerator来迭代。所有数据和判断都封装在了MoveNext方法里面。

2.迭代一个类,和迭代方法有些许不同,但是基本原理是一样的C#如下,

Program.cs文件内容

 1 namespace Ch11Ex03
 2 {
 3     class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             Primes primesFrom2To1000 = new Primes(2, 1000);
 8             foreach (long i in primesFrom2To1000)
 9                 Console.Write("{0} ", i);
10 
11             Console.ReadKey();
12         }
13     }
14 }
View Code

Primes.cs文件内容

 1 namespace Ch11Ex03
 2 {
 3     public class Primes
 4     {
 5         private long min;
 6         private long max;
 7 
 8         public Primes()
 9             : this(2, 100)
10         {
11         }
12 
13         public Primes(long minimum, long maximum)
14         {
15             if (min < 2)
16                 min = 2;
17             else
18                 min = minimum;
19 
20             max = maximum;
21         }
22 
23         public IEnumerator GetEnumerator()
24         {
25             for (long possiblePrime = min; possiblePrime <= max; possiblePrime++)
26             {
27                 bool isPrime = true;
28                 for (long possibleFactor = 2; possibleFactor <=
29                    (long)Math.Floor(Math.Sqrt(possiblePrime)); possibleFactor++)
30                 {
31                     long remainderAfterDivision = possiblePrime % possibleFactor;
32                     if (remainderAfterDivision == 0)
33                     {
34                         isPrime = false;
35                         break;
36                     }
37                 }
38                 if (isPrime)
39                 {
40                     yield return possiblePrime;
41                 }
42             }
43         }
44     }
45 }
View Code

Program.cs中Main函数的IL代码如下:

 1 .method private hidebysig static void  Main(string[] args) cil managed
 2 {
 3   .entrypoint
 4   // 代码大小       100 (0x64)
 5   .maxstack  3
 6   .locals init ([0] class Ch11Ex03.Primes primesFrom2To1000,
 7            [1] int64 i,
 8            [2] class [mscorlib]System.Collections.IEnumerator CS$5$0000,
 9            [3] bool CS$4$0001,
10            [4] class [mscorlib]System.IDisposable CS$0$0002)
11   IL_0000:  nop
12   IL_0001:  ldc.i4.2
13   IL_0002:  conv.i8
14   IL_0003:  ldc.i4     0x3e8
15   IL_0008:  conv.i8
16   IL_0009:  newobj     instance void Ch11Ex03.Primes::.ctor(int64,
17                                                             int64)
18   IL_000e:  stloc.0
19   IL_000f:  nop
20   IL_0010:  ldloc.0
21   IL_0011:  callvirt   instance class [mscorlib]System.Collections.IEnumerator Ch11Ex03.Primes::GetEnumerator()
22   IL_0016:  stloc.2
23   .try
24   {
25     IL_0017:  br.s       IL_0036
26     IL_0019:  ldloc.2
27     IL_001a:  callvirt   instance object [mscorlib]System.Collections.IEnumerator::get_Current()
28     IL_001f:  unbox.any  [mscorlib]System.Int64
29     IL_0024:  stloc.1
30     IL_0025:  ldstr      "{0} "
31     IL_002a:  ldloc.1
32     IL_002b:  box        [mscorlib]System.Int64
33     IL_0030:  call       void [mscorlib]System.Console::Write(string,
34                                                               object)
35     IL_0035:  nop
36     IL_0036:  ldloc.2
37     IL_0037:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
38     IL_003c:  stloc.3
39     IL_003d:  ldloc.3
40     IL_003e:  brtrue.s   IL_0019
41     IL_0040:  leave.s    IL_005c
42   }  // end .try
43   finally
44   {
45     IL_0042:  ldloc.2
46     IL_0043:  isinst     [mscorlib]System.IDisposable
47     IL_0048:  stloc.s    CS$0$0002
48     IL_004a:  ldloc.s    CS$0$0002
49     IL_004c:  ldnull
50     IL_004d:  ceq
51     IL_004f:  stloc.3
52     IL_0050:  ldloc.3
53     IL_0051:  brtrue.s   IL_005b
54     IL_0053:  ldloc.s    CS$0$0002
55     IL_0055:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
56     IL_005a:  nop
57     IL_005b:  endfinally
58   }  // end handler
59   IL_005c:  nop
60   IL_005d:  call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
61   IL_0062:  pop
62   IL_0063:  ret
63 } // end of method Program::Main
View Code

由上可以看出和迭代方法生成的IL代码没有多大区别。

Primes类生成的IL代码如下:

再看实现迭代的方法IEnumerator中的IL代码如下:

 1 .method public hidebysig instance class [mscorlib]System.Collections.IEnumerator 
 2         GetEnumerator() cil managed
 3 {
 4   // 代码大小       20 (0x14)
 5   .maxstack  2
 6   .locals init ([0] class Ch11Ex03.Primes/'<GetEnumerator>d__0' V_0,
 7            [1] class [mscorlib]System.Collections.IEnumerator V_1)
 8   IL_0000:  ldc.i4.0
 9   IL_0001:  newobj     instance void Ch11Ex03.Primes/'<GetEnumerator>d__0'::.ctor(int32)
10   IL_0006:  stloc.0
11   IL_0007:  ldloc.0
12   IL_0008:  ldarg.0
13   IL_0009:  stfld      class Ch11Ex03.Primes Ch11Ex03.Primes/'<GetEnumerator>d__0'::'<>4__this'
14   IL_000e:  ldloc.0
15   IL_000f:  stloc.1
16   IL_0010:  br.s       IL_0012
17   IL_0012:  ldloc.1
18   IL_0013:  ret
19 } // end of method Primes::GetEnumerator
View Code

可以看出这个方法实例化了<GetEnumerator>d__0类的一个实例并返回它,而且这里还初始化了 Primes类的实例到编译器生成的类的字段Ch11Ex03.Primes/'<GetEnumerator>d__0'::'<>4__this'中,主要原因是由于在IEnumerator中使用到了外部类的实例字段,所以要在内部维护对外部对象的一个引用。从这里再一次可以看出迭代器将所有的算法和逻辑判断以及数据封装都放入了MoveNext方法中。

总结1:从以上可以看出要使用foreach迭代一个方法,必须让方法返回IEnumerable或IEnumerable<T>接口。而迭代一个类,要么让类实现IEnumerable或IEnumerable<T>的接口,重写其接口的方法,要么写一个名为GetEnumerator的方法,返回IEnumerator或IEnumerator<T>接口。主要原因都是foreach生成的代码进行迭代时,需要使用GetEnumerator方法返回迭代器接口进行迭代。

3.但是若是不使用foreach进行自动迭代,而要手动进行迭代一个方法或类,那么只要实现一个迭代的方法(这个方法名无所谓)并返回IEnumerator或IEnumerator<T>接口就可以了。C#如下 :

 1  static void Main(string[] args)
 2         {
 3        
 4             var ienumerator = IteratorManual();
 5             while (ienumerator.MoveNext())
 6             {
 7                 Console.WriteLine(ienumerator.Current);
 8             }
 9             Console.Read();
10         }
11 
12         private static IEnumerator IteratorManual()
13         {
14             yield return "5";
15             yield return 2;
16 
17         }
View Code

生成后的main方法的IL代码如下 :

 1 .method private hidebysig static void Main(string[] args) cil managed
 2 {
 3     .entrypoint
 4     .maxstack 1
 5     .locals init (
 6         [0] class [mscorlib]System.Collections.IEnumerator ienumerator,
 7         [1] bool CS$4$0000)
 8     L_0000: nop 
 9     L_0001: call class [mscorlib]System.Collections.IEnumerator VS2010.ConsoleApp.Iterator.Program::IteratorManual()
10     L_0006: stloc.0 
11     L_0007: br.s L_0017
12     L_0009: nop 
13     L_000a: ldloc.0 
14     L_000b: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
15     L_0010: call void [mscorlib]System.Console::WriteLine(object)
16     L_0015: nop 
17     L_0016: nop 
18     L_0017: ldloc.0 
19     L_0018: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
20     L_001d: stloc.1 
21     L_001e: ldloc.1 
22     L_001f: brtrue.s L_0009
23     L_0021: call int32 [mscorlib]System.Console::Read()
24     L_0026: pop 
25     L_0027: ret 
26 }
27 
28  
View Code

手动实现的迭代和用foreach实现的迭代生成的il基本差不多,只是在获得IEnumerator接口时调用的方法不一样而已,因为foreach迭代想获得IEnumerator,它必须要有一个规范,实现统一的接口,所以有约束,但是手动进行迭代就不必这么麻烦。

而IteratorManual()方法同样会生成一个类并返回这个类的实例,如下IL代码:

 注意:有一种情况,在迭代一个类或方法时,不会为需要进行迭代的方法生成内部类,那就是在需要迭代的方法内部返回了一个可迭代的集合的IEnumerator或IEnumerator接口,如:

1  private IEnumerator IteratorCollection()
2         {
3             var list = new List<int> { 2, 3, 4 };
4             return list.GetEnumerator();
5         }
View Code

 

 

 

posted @ 2013-10-29 22:23  BigDataForFuture  Views(576)  Comments(0)    收藏  举报