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

如何在C#中生成枚举器的枚举器?.NET学习者技术问询

在C#中实现分段迭代的标准做法

Hey there! 作为常年跟.NET打交道的开发者,我太懂从Python转过来时对迭代器实现的困惑了——两种语言的迭代逻辑确实有不少差异。针对你提到的分段迭代需求,我来拆解下C#里的标准解法,顺便解决你担心的实例过多问题。

先解决你最关心的:避免大量Segment实例的方案

你担心用new Segment会在大数据量下产生太多实例,其实完全可以通过延迟迭代的方式绕开这个问题,不需要提前创建任何分段对象,而是在迭代过程中动态生成每一段的元素。

这里有个通用的扩展方法实现,适用于所有IEnumerable<T>类型:

public static IEnumerable<IEnumerable<T>> Slice<T>(this IEnumerable<T> source, int sliceSize)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (sliceSize <= 0) throw new ArgumentOutOfRangeException(nameof(sliceSize));

    using var enumerator = source.GetEnumerator();
    while (enumerator.MoveNext())
    {
        yield return GetNextSlice(enumerator, sliceSize);
    }
}

private static IEnumerable<T> GetNextSlice<T>(IEnumerator<T> enumerator, int sliceSize)
{
    int currentCount = 0;
    do
    {
        yield return enumerator.Current;
        currentCount++;
    } while (currentCount < sliceSize && enumerator.MoveNext());
}

这个实现的核心是延迟执行:

  • 不会一次性把整个序列拆分成所有分段,而是每次迭代到下一段时才生成对应的元素;
  • 不需要任何自定义的Segment类,完全靠枚举器的状态控制分段,从根源上避免了大量实例的产生。

回应你的两个核心诉求

  1. 分段大小与最后一段截断:
    上面的代码会自动处理最后一段的截断——当枚举器走到序列末尾时,即使没凑够sliceSize个元素,也会停止并返回当前已有的元素。如果需要明确获取每段的大小,可以稍微修改返回类型,比如返回IEnumerable<(int SliceSize, IEnumerable<T> Elements)>,这样既不用额外类,又能拿到分段信息:

    public static IEnumerable<(int SliceSize, IEnumerable<T> Elements)> SliceWithSize<T>(this IEnumerable<T> source, int sliceSize)
    {
        using var enumerator = source.GetEnumerator();
        while (enumerator.MoveNext())
        {
            var slice = GetNextSlice(enumerator, sliceSize).ToList();
            yield return (slice.Count, slice.AsEnumerable());
        }
    }
    
  2. 能否直接yield枚举器:
    在C#里你不能直接yield return一个IEnumerator<T>,但可以yield return一个IEnumerable<T>(就像上面的GetNextSlice方法)。因为GetNextSlice本身就是一个延迟执行的迭代器,当外层迭代器遍历到它时,才会开始枚举里面的元素,完全符合你想要的迭代器行为。

针对有索引的集合的优化方案

如果你的输入是数组、List<T>这种支持索引访问的集合,还有更轻量的选择——用ArraySegment<T>(值类型,堆上无额外开销):

public static IEnumerable<ArraySegment<T>> Slice<T>(this T[] source, int sliceSize)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (sliceSize <= 0) throw new ArgumentOutOfRangeException(nameof(sliceSize));

    for (int i = 0; i < source.Length; i += sliceSize)
    {
        int currentSliceSize = Math.Min(sliceSize, source.Length - i);
        yield return new ArraySegment<T>(source, i, currentSliceSize);
    }
}

ArraySegment<T>只是对原数组的一个“视图”,不会复制元素,性能非常好,就算处理超大数组也不用担心实例开销。

总结一下最优实践

  • 对于通用IEnumerable<T>:优先用第一个延迟迭代的方案,无额外实例,全延迟执行;
  • 对于数组/List<T>:用ArraySegment<T>,轻量高效,还能直接拿到分段的索引和大小;
  • 除非有特殊业务逻辑需要自定义分段属性,否则尽量避免自己写Segment类——C#已经提供了足够好用的工具。

内容的提问来源于stack exchange,提问作者RandomB

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:33:06