C# yeild的使用
yield是 C# 中一个非常巧妙的语法糖,它的核心作用是实现“延迟执行(Lazy Evaluation)”。
简单来说,它允许你在一个方法中逐个返回数据,而不需要一次性把所有数据都计算好并塞进一个List里返回。
🎬 核心概念:从“批发”到“零售”
为了理解yield,我们先看一个不使用yield的传统写法:
// 传统写法:一次性生成所有数据(批发)publicList<int>GetNumbers(intcount){varlist=newList<int>();for(inti=0;i<count;i++){list.Add(i);// 把所有数字都塞进内存里的集合}returnlist;// 最后一次性返回整个集合}缺点:如果count是 100 万,这个方法会瞬间在内存中分配一个包含 100 万个元素的数组,造成极大的内存浪费。
现在看看使用yield的写法:
// yield 写法:按需生成数据(零售)publicIEnumerable<int>GetNumbersYield(intcount){for(inti=0;i<count;i++){yieldreturni;// 每次吐出一个数字,然后“暂停”在这里}}优点:调用这个方法时,它根本不会立刻执行循环。只有当外部真正去读取下一个数据时,它才会执行一次循环,吐出一个值,然后暂停。内存中永远只存在当前这一个数字。
🛠️ 两个核心关键字
yield return:返回当前值,并记住当前的执行状态(包括局部变量、循环索引等)。下次调用时,直接从这行代码的下一行继续执行。yield break:提前结束迭代,相当于普通方法里的return。
💡 典型应用场景
1. 读取超大文件或数据流
假设你要读取一个 10GB 的日志文件,如果用File.ReadAllLines(),内存直接撑爆。用yield可以逐行处理:
publicIEnumerable<string>ReadLargeFile(stringfilePath){using(varreader=newStreamReader(filePath)){stringline;while((line=reader.ReadLine())!=null){yieldreturnline;// 读一行,吐出一行,内存占用极小}}}// 使用时:foreach(varlineinReadLargeFile("huge_log.txt")){if(line.Contains("ERROR"))Console.WriteLine(line);}2. 实现自定义迭代器
如果你想让你自己写的类支持foreach循环,就可以用yield:
publicclassCountdown{privateint_start;publicCountdown(intstart){_start=start;}publicIEnumerator<int>GetEnumerator(){for(inti=_start;i>=0;i--){yieldreturni;}}}// 使用时:varcountdown=newCountdown(5);foreach(varnumincountdown){Console.WriteLine(num);// 输出 5, 4, 3, 2, 1, 0}3. 提前终止(yield break)
publicIEnumerable<int>GetEvenNumbers(intmax){for(inti=0;i<=max;i++){if(i>10)yieldbreak;// 如果超过10,直接结束整个迭代if(i%2==0)yieldreturni;}}⚠️ 必须注意的“坑”
- 只能返回特定类型:包含
yield的方法,返回类型只能是IEnumerable、IEnumerable<T>、IEnumerator或IEnumerator<T>。不能直接返回List<T>或数组。 - 延迟执行的副作用:
如果你在varquery=GetNumbersYield(100);// 此时循环一次都没执行!// 只有走到这里,循环才开始foreach(variteminquery){...}GetNumbersYield里面有参数校验(比如if(count < 0) throw...),在调用方法时不会报错,只有在foreach第一次迭代时才会报错。 - 不要在
yield方法里使用try-catch-finally包裹yield return:C# 编译器不允许在包含yield return的try块中使用catch(但可以使用finally)。
📌 总结
yield的本质是状态机。C# 编译器在底层帮你把yield方法编译成了一个复杂的类,自动帮你保存了局部变量和执行位置。
一句话口诀:当数据量很大,或者数据是按需生成的,且你只需要用foreach去遍历它时,果断使用yield return。
