You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在单次循环中用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 16:07:35