一直对迭代器处于能用的层次,这次准备深入探索下迭代器的实现原理。
在.net中,一个类实现了IEnumerable
接口,那它就可以被迭代。在IEnumerable
中只包含了一个接口方法需要被实现:IEnumerator GetEnumerator();
,而返回的就是实现了IEnumerator
接口的对象。
IEnumerable
与IEnumerator
的关系,犹如:一本书与一个书签。IEnumerator
记录当前的位置,其每次调用MoveNext(),书签向前移动一个位置。当然一个IEnumerable
中可以有多个IEnumerator
。
从C#1时代foreach
就已经实现了对迭代器的支持。
C#1手写一个迭代器 在C# 1时代没有 yield 关键字,手写一个迭代器编码量还是多了挺多的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 public class MyEnumerable : IEnumerable <string >{ private readonly List<string > datas; public MyEnumerable (List<string > datas ) { this .datas = datas; } public IEnumerator<string > GetEnumerator () { return new MyEnumerator(datas); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } internal class MyEnumerator : IEnumerator <string > { private readonly List<string > _list; private int _state; public MyEnumerator (List<string > datas ) { _list = datas; _state = -1 ; } object IEnumerator.Current => Current; public string Current{ get { if (_state == -1 || _state >= _list.Count) { throw new InvalidOperationException(); } return _list[_state]; } } public bool MoveNext () { if (_state != _list.Count) { _state++; } return _state < _list.Count; } public void Reset () { _state = -1 ; } } }
C#2使用yield简化上面迭代器 使用yield
关键字告诉编译器:这是一个需要执行的迭代块。
1 2 3 4 5 6 7 static IEnumerable<int > GetEnumerable (){ for (int i = 0 ; i < datas.Count; i++) { yield return datas[i]; } }
用IL看看编译后生成的代码啥样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 [CompilerGenerated ] private sealed class <GetEnumerable >d__1 : IEnumerable <int >, IEnumerable , IEnumerator <int >, IEnumerator , IDisposable { private int <>1 __state; private int <>2 __current; private int <>l__initialThreadId; private int <i>5 __2; int IEnumerator<int >.Current { [DebuggerHidden ] get { return <>2 __current; } } object IEnumerator.Current { [DebuggerHidden ] [return: Nullable(0) ] get { return <>2 __current; } } [DebuggerHidden ] public <GetEnumerable>d__1(int <>1 __state) { this .<>1 __state = <>1 __state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden ] void IDisposable.Dispose() { } private bool MoveNext () { switch (<>1 __state) { default : return false ; case 0 : <>1 __state = -1 ; <i>5 __2 = 0 ; break ; case 1 : <>1 __state = -1 ; <i>5 __2++; break ; } if (<i>5 __2 < 100 ) { <>2 __current = <i>5 __2; <>1 __state = 1 ; return true ; } return false ; } bool IEnumerator.MoveNext() { return this .MoveNext(); } [DebuggerHidden ] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden ] IEnumerator<int > IEnumerable<int >.GetEnumerator() { if (<>1 __state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1 __state = 0 ; return this ; } return new <GetEnumerable>d__1(0 ); } [DebuggerHidden ] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<int >)this ).GetEnumerator(); } } private static void Main (string [] args ){ foreach (int item in GetEnumerable ()) { Console.WriteLine(item); } } [IteratorStateMachine(typeof(<GetEnumerable>d__1)) ] private static IEnumerable<int > GetEnumerable (){ return new <GetEnumerable>d__1(-2 ); }
迭代器的调用步骤 网上有个挺好的例子,借鉴下。可以得出以下结论:
调用CreateEnumerable()并不会执行内部方法,而是在首次 MoveNext() 时才进行执行。
代码执行到 yield return 后就会停止向下执行,而是等到再次调用MoveNext()才会继续执行。
在一个方法中可以多处进行 yield return 语句。
代码不会在 yield return 时结束,而是在 MoveNext() 返回false时结束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 class Program { private static readonly string Padding = new string (' ' , 30 ); static void Main (string [] args ) { IEnumerable<int > iterable = CreateEnumerable(); IEnumerator<int > iterator = iterable.GetEnumerator(); Console.WriteLine("start to iterate" ); while (true ) { Console.WriteLine("calling MoveNext()...." ); bool moveNext = iterator.MoveNext(); Console.WriteLine($"....MoveNext result={moveNext} " ); if (!moveNext) { break ; } Console.WriteLine("fetching current value...." ); Console.WriteLine($"current value={iterator.Current} " ); } Console.ReadKey(); } static IEnumerable<int > CreateEnumerable () { Console.WriteLine($"{Padding} start of CreateEnumerable()" ); for (int i = 0 ; i < 3 ; i++) { Console.WriteLine($"{Padding} about to yield {i} " ); yield return i; Console.WriteLine($"{Padding} after yield" ); } Console.WriteLine($"{Padding} yielding final value" ); yield return -1 ; Console.WriteLine($"{Padding} End of CreateEnumerable()" ); } }