C# Yield

#CSharp #yield
yield return
理解及应用
yield就是生成了一个值之后,控制回到了调用方继续执行,执行完成后,需要下一个值的时候,会重新回到yield return之后的语句处,继续执行。
结合下面的程序,理解了这点之后,跟yield相关的知识基本就都了解了。
代码:
public static void Main()
{
var testClass = new TestClass();
Console.WriteLine("[Main] BeginTest");
foreach (var value in testClass)
{
Console.WriteLine($"[Main] Got Value {value}");
Console.WriteLine("[Main] Do some logic here");
Console.WriteLine("[Main] Try to get next value");
}
}
public class TestClass : IEnumerable<int>
{
public IEnumerator<int> GetEnumerator()
{
Console.WriteLine("[Class] Begin Generate");
for (var index = 0; index < 3; index++)
{
Console.WriteLine($"[Class] Generate {index}");
yield return index;
Console.WriteLine($"[Class] Finished send {index}");
}
Console.WriteLine("[Class] Finished Generate");
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
程序执行日志:
[Main] BeginTest
[Class] Begin Generate
[Class] Generate 0
[Main] Got Value 0
[Main] Do some logic here
[Main] Try to get next value
[Class] Finished send 0
[Class] Generate 1
[Main] Got Value 1
[Main] Do some logic here
[Main] Try to get next value
[Class] Finished send 1
[Class] Generate 2
[Main] Got Value 2
[Main] Do some logic here
[Main] Try to get next value
[Class] Finished send 2
[Class] Finished Generate
递归迭代器
代码:
public static void Main()
{
var testClass = new TestClass();
testClass.Value = 1;
testClass.SubClasses = new List<TestClass>
{
new()
{
Value = 2,
SubClasses = new List<TestClass>
{
new() { Value = 3 },
new() { Value = 4 }
}
},
new() { Value = 5 }
};
foreach (var item in testClass)
{
Console.WriteLine(item);
}
}
public class TestClass : IEnumerable<int>
{
public IList<TestClass> SubClasses { get; set; }
public int Value { get; set; }
public IEnumerator<int> GetEnumerator()
{
yield return Value;
if (SubClasses is not { Count: > 0 }) { yield break; }
foreach (var subClass in SubClasses)
{
foreach (var item in subClass)
{
yield return item;
}
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
程序执行日志:
1
2
3
4
5
注意
递归迭代器存在一定的风险,如果是不平衡二叉树的话,可能会很深,获取值的时候的递归迭代的成本较高。
原理
C# 编译器在遇到迭代器的时候,会根据枚举数模式(Iterator Pattern)将代码展开成CLI。
在生成的代码中,C# 编译器会首先创建一个嵌套的私有类来实现 IEnumerator<T> 接口,以及它的 Current 属性和 MoveNext() 方法。
Current 属性返回与迭代器的返回类型对应的一个类型。
C# 编译器检查包含在迭代器中的代码,并在 MoveNext() 方法和 Current 属性中创建必要的代码来模拟它的行为。
yield break
取消更多的迭代
注意
yield语句只能在方法、用户自定义操作符、或者索引器或属性的get防蚊器方法中出现。成员不可以获取任何ref或out参数yield语句不能在匿名语句或者lambda方法中出现yield语句不能在try语句的catch和final子句中出现。除此之外,yield语句能在try块中出现的前提是,它没有catch块




