.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
从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
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 }
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 }
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
由上可以看出和迭代方法生成的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
可以看出这个方法实例化了<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 }
生成后的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
手动实现的迭代和用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 }

浙公网安备 33010602011771号