6.2 C# 2:利用 yield 语句简化迭代器
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 object[] values = new object[] { "a", "b", "c", "d", "e" }; 6 IterationSample sample = new IterationSample(values, 3); 7 foreach (var item in sample) 8 { 9 Console.WriteLine(item); 10 } 11 Console.ReadKey(); 12 } 13 } 14 public class IterationSample : IEnumerable 15 { 16 public object[] values; 17 int startPoint; 18 public IterationSample(object[] values, int startingPoint) 19 { 20 this.values = values; 21 this.startPoint = startingPoint; 22 } 23 public IEnumerator GetEnumerator() 24 { 25 //return new IterationSampleIterator(this); 26 for (int index = 0; index < values.Length; index++) 27 { 28 yield return values[(index + startPoint) % values.Length]; 29 } 30 } 31 } 32 public class IterationSampleIterator : IEnumerator 33 { 34 IterationSample parent; 35 int position; 36 public IterationSampleIterator(IterationSample parent) 37 { 38 this.parent = parent; 39 this.position = -1; 40 } 41 public object Current 42 { 43 get 44 { 45 if (position == -1 || position == parent.values.Length) 46 { 47 throw new InvalidOperationException(); 48 } 49 int index = position + parent.values.Length; 50 index = index % parent.values.Length; 51 return parent.values[index]; 52 } 53 } 54 55 public bool MoveNext() 56 { 57 if (position != parent.values.Length) 58 { 59 position++; 60 } 61 return position < parent.values.Length; 62 } 63 64 public void Reset() 65 { 66 position = -1; 67 } 68 }
6.2.2 观察迭代器的工作流程
1 class Program 2 { 3 static readonly string Padding = new string(' ', 30); 4 static void Main(string[] args) 5 { 6 IEnumerable<int> iterable = CreatteEnumerable(Padding); 7 IEnumerator<int> iterator = iterable.GetEnumerator(); 8 Console.WriteLine("starting iterate"); 9 10 while (true) 11 { 12 Console.WriteLine("======================================="); 13 Console.WriteLine("calling MoveNext()"); 14 bool result = iterator.MoveNext(); 15 Console.WriteLine("moveNext result = {0}", result); 16 if (!result) 17 break; 18 Console.WriteLine("fetching current"); 19 Console.WriteLine("current result = {0}", iterator.Current); 20 } 21 22 Console.ReadKey(); 23 } 24 static IEnumerable<int> CreatteEnumerable(string Padding) 25 { 26 Console.WriteLine("{0} start of createEnumerbale padding", Padding); 27 28 for (int i = 0; i < 3; i++) 29 { 30 Console.WriteLine("{0} about to yield {1}", Padding, i); 31 yield return i; 32 Console.WriteLine("{0} after padding", Padding); 33 } 34 Console.WriteLine("{0} yield final value ", Padding); 35 yield return -1; 36 Console.WriteLine("{0} end of createEnumerable();", Padding); 37 } 38 /* 39 40 starting iterate 41 ======================================= 42 calling MoveNext() 43 start of createEnumerbale padding 44 about to yield 0 45 moveNext result = True 46 fetching current 47 current result = 0 48 ======================================= 49 calling MoveNext() 50 after padding 51 about to yield 1 52 moveNext result = True 53 fetching current 54 current result = 1 55 ======================================= 56 calling MoveNext() 57 after padding 58 about to yield 2 59 moveNext result = True 60 fetching current 61 current result = 2 62 ======================================= 63 calling MoveNext() 64 after padding 65 yield final value 66 moveNext result = True 67 fetching current 68 current result = -1 69 ======================================= 70 calling MoveNext() 71 end of createEnumerable(); 72 moveNext result = False 73 74 */ 75 }
6.2.3 进一步了解迭代器执行流程
1. 使用 yield break 结束迭代器的执行
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 DateTime stop = DateTime.Now.AddSeconds(2); 6 foreach (var item in CountWithTimeLimit(stop)) 7 { 8 Console.WriteLine("received {0}", item); 9 Thread.Sleep(300); 10 } 11 Console.ReadKey(); 12 } 13 static IEnumerable<int> CountWithTimeLimit(DateTime limit) 14 { 15 for (int i = 0; i < 100; i++) 16 { 17 if (DateTime.Now >= limit) 18 { 19 yield break; 20 } 21 yield return i; 22 } 23 } 24 }
2. finally 代码块的执行
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 DateTime stop = DateTime.Now.AddSeconds(2); 6 foreach (var item in CountWithTimeLimit(stop)) 7 { 8 Console.WriteLine("received {0}", item); 9 if (item > 3) 10 { 11 Console.WriteLine("returning"); 12 return; 13 } 14 Thread.Sleep(300); 15 } 16 Console.ReadKey(); 17 } 18 static IEnumerable<int> CountWithTimeLimit(DateTime limit) 19 { 20 try 21 { 22 for (int i = 0; i < 100; i++) 23 { 24 if (DateTime.Now >= limit) 25 { 26 yield break; 27 } 28 yield return i; 29 } 30 } 31 finally 32 { 33 Console.WriteLine("stopping"); 34 Console.ReadKey(); 35 } 36 } 37 /* 38 received 0 39 received 1 40 received 2 41 received 3 42 received 4 43 returning 44 stopping 45 */ 46 }
foreach 会在它自己的 finally 代码块中调用 IEnumerator 所提供的Dispose 方法(就像 using 语句)。
当迭代器完成迭代之前,你如果调用由迭代器代码块创建的迭代器上的 Dispose ,
那么状态机就会执行在代码当前“暂停”位置范围内的任何 finally 代码块。
这个解释复杂且有点详细,但结果却很容易描述:只要调用者使用了 foreach 循环,迭代器块中的 finally 将按照你期望的方式工作。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 DateTime stop = DateTime.Now.AddSeconds(2); 6 IEnumerable<int> iterable = CountWithTimeLimit(stop); 7 IEnumerator<int> iterator = iterable.GetEnumerator(); 8 9 iterator.MoveNext(); 10 Console.WriteLine("received {0}", iterator.Current); 11 12 iterator.MoveNext(); 13 Console.WriteLine("received {0}", iterator.Current); 14 15 Console.ReadKey(); 16 } 17 static IEnumerable<int> CountWithTimeLimit(DateTime limit) 18 { 19 try 20 { 21 for (int i = 0; i < 100; i++) 22 { 23 if (DateTime.Now >= limit) 24 { 25 yield break; 26 } 27 yield return i; 28 } 29 } 30 finally 31 { 32 Console.WriteLine("stopping"); 33 Console.ReadKey(); 34 } 35 } 36 /* 37 received 0 38 received 1 39 */ 40 }
幸好,作为开发人员我们不需要太关心编译器是如何解决这些问题的。不过,关于实现中的以下一些奇特之处还是值得了解的:
在第一次调用 MoveNext 之前, Current 属性总是返回迭代器产生类型的默认值;
在 MoveNext 返回 false 之后, Current 属性总是返回最后的生成值;
Reset 总是抛出异常,而不像我们手动实现的重置过程那样,为了遵循语言规范,这是必要的行为;
嵌套类总是实现 IEnumerator 的泛型形式和非泛型形式(提供给泛型和非泛型的IEnumerable 所用)。
6.3.2 迭代文件中的行
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 string fileName = string.Format(@"{0}aaa.txt", AppDomain.CurrentDomain.BaseDirectory); 6 foreach (var item in ReadLines(fileName)) 7 { 8 Console.WriteLine(item); 9 } 10 11 Console.ReadKey(); 12 } 13 static IEnumerable<string> ReadLines(string fileName) 14 { 15 using (TextReader reader = File.OpenText(fileName)) 16 { 17 string line; 18 while ((line = reader.ReadLine()) != null) 19 { 20 yield return line; 21 } 22 } 23 } 24 }
6.3.3 使用迭代器块和谓词对项进行延迟过滤
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 string fileName = string.Format(@"{0}aaa.txt", AppDomain.CurrentDomain.BaseDirectory); 6 7 IEnumerable<string> lines = ReadLines(fileName); 8 Predicate<string> predicate = line => line.StartsWith("using"); 9 10 foreach (var item in Where(lines, predicate)) 11 { 12 Console.WriteLine(item); 13 } 14 15 Console.ReadKey(); 16 } 17 public static IEnumerable<T> Where<T>(IEnumerable<T> source, Predicate<T> predicate) 18 { 19 if (source.IsNull() || predicate.IsNull()) 20 throw new ArgumentException(); 21 22 return WhereImpl(source, predicate); 23 } 24 private static IEnumerable<T> WhereImpl<T>(IEnumerable<T> source, Predicate<T> predicate) 25 { 26 foreach (T item in source) 27 { 28 if (predicate(item)) 29 { 30 yield return item; 31 } 32 } 33 } 34 static IEnumerable<string> ReadLines(string fileName) 35 { 36 return ReadLines(() => { return File.OpenText(fileName); }); 37 } 38 static IEnumerable<string> ReadLines(Func<TextReader> provider) 39 { 40 using (TextReader reader = provider()) 41 { 42 string line; 43 while ((line = reader.ReadLine()) != null) 44 { 45 yield return line; 46 } 47 } 48 } 49 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了