如何在单次循环中用yield return返回两个IEnumerable集合?
问题描述
我原本有一段通过yield return实现延迟返回IEnumerable<Class1>的代码:
public IEnumerable<Class1> DoSmth() { foreach (...) { output = DoComplexCalc(); yield return new Class1(output); } yield break; }
现在我希望保留延迟执行(yield的特性),但需要同时返回两个集合,目前的实现是提前把所有元素存入List后返回,无法享受延迟执行的优势:
public (IEnumerable<Class1>, IEnumerable<Class2>) DoSmth() { var list1 = new List<Class1>(); var list2 = new List<Class2>(); foreach (...) { output = DoComplexCalc(); if (output.Whatever) { list1.Add(new Class1(output)); } else { list2.Add(new Class2(output)); } } return (list1, list2); }
我不想拆分出两个独立函数(避免执行两次DoComplexCalc()循环),请问如何在只执行一次循环的前提下,保留延迟执行的特性?
解决方案:共享迭代器状态
核心思路是创建一个共享状态类,让两个返回的IEnumerable共用同一个迭代过程,确保仅执行一次DoComplexCalc()循环,同时保留延迟生成元素的特性。
1. 定义共享状态类
这个类负责执行迭代逻辑,缓存每次生成的结果,供两个枚举器按需取用:
private class DualIteratorState { private readonly IEnumerator<OutputType> _sourceEnumerator; public Class1 CurrentItem1 { get; private set; } public Class2 CurrentItem2 { get; private set; } public DualIteratorState(IEnumerable<OutputType> source) { _sourceEnumerator = source.GetEnumerator(); } public bool MoveNext() { CurrentItem1 = null; CurrentItem2 = null; if (!_sourceEnumerator.MoveNext()) return false; var output = _sourceEnumerator.Current; if (output.Whatever) { CurrentItem1 = new Class1(output); } else { CurrentItem2 = new Class2(output); } return true; } public void Dispose() { _sourceEnumerator.Dispose(); } }
注:将OutputType替换为DoComplexCalc()实际返回的类型
2. 实现双延迟枚举的方法
在DoSmth()中创建共享状态实例,为两个集合分别返回绑定该状态的迭代器:
public (IEnumerable<Class1>, IEnumerable<Class2>) DoSmth() { var source = GetCalculationSource(); var state = new DualIteratorState(source); var sequence1 = IterateSequence1(state); var sequence2 = IterateSequence2(state); return (sequence1, sequence2); } // 封装循环与计算逻辑的私有延迟序列 private IEnumerable<OutputType> GetCalculationSource() { foreach (...) // 替换为你的实际循环条件 { var output = DoComplexCalc(); yield return output; } } // 生成Class1序列的迭代逻辑 private IEnumerable<Class1> IterateSequence1(DualIteratorState state) { try { while (state.MoveNext()) { if (state.CurrentItem1 != null) yield return state.CurrentItem1; } } finally { state.Dispose(); } } // 生成Class2序列的迭代逻辑 private IEnumerable<Class2> IterateSequence2(DualIteratorState state) { try { while (state.MoveNext()) { if (state.CurrentItem2 != null) yield return state.CurrentItem2; } } finally { state.Dispose(); } }
关键特性说明
- 延迟执行:只有当任意一个返回的
IEnumerable开始被枚举时,GetCalculationSource()中的循环和DoComplexCalc()才会启动。 - 单次计算:两个枚举器共享同一个状态实例,循环只会执行一次,每次生成的结果会被缓存供双方取用。
- 资源管理:通过
finally块确保迭代器资源被正确释放。
简化方案:结合二元组与过滤
如果不需要极致的延迟触发(比如允许枚举第一个序列时一次性完成所有计算),可以用更简洁的写法:
public (IEnumerable<Class1>, IEnumerable<Class2>) DoSmth() { var combined = GetCombinedSequence(); return ( combined.Where(x => x.Item1 != null).Select(x => x.Item1), combined.Where(x => x.Item2 != null).Select(x => x.Item2) ); } private IEnumerable<(Class1, Class2)> GetCombinedSequence() { foreach (...) { var output = DoComplexCalc(); if (output.Whatever) yield return (new Class1(output), null); else yield return (null, new Class2(output)); } }
这种方式代码更短,且同样保证只执行一次DoComplexCalc()循环,Where和Select的延迟特性也能避免提前创建完整集合。
内容的提问来源于stack exchange,提问作者Ash
相关产品推荐
相关产品推荐

