如何防止枚举器聚合所有项目?

How do I prevent the enumerator from aggregating all items?

我有一个简单的函数,我只是在这里用作示例。是无限枚举!

public static IEnumerable<int> Roll()
{
    while (true)
    {
        yield return new Random().Next(1, 6);
    }
}

而且我希望这个函数在特定数量的时钟滴答中保持滚动,所以我有这个方法:

    public static List<T> Ticks<T>(this IEnumerable<T> data, int time)
    {
        var list = new List<T>();
        Stopwatch timer = null;
        foreach (var item in data)
        {
            if (timer == null)
            {
                timer = Stopwatch.StartNew();
            }

            if (timer.ElapsedTicks < time)
            {
                list.Add(item);
            }
            else
            {
                break;
            }
        }
        timer?.Stop();
        return list;
    }

但我真的不喜欢我必须写的方式,因为我更喜欢这样的东西:

    public static List<T> Ticks2<T>(this IEnumerable<T> data, int time)
    {
        var list = new List<T>();
        Stopwatch timer = Stopwatch.StartNew();
        using (var en = data.GetEnumerator())
        {
            while (timer.ElapsedTicks < time && en.MoveNext())
            {
                list.Add(en.Current);
            }
        }
        timer.Stop();
        return list;
    }

但是后一个函数将失败,因为 GetEnumerator() 方法将聚合所有数据并在不定式枚举中,这需要永远...
等等...我错了...原来这么快我的列表运行内存不足...不是聚合,还好...
Ticks() 方法工作正常,顺便说一句!我可以用这个:

Console.WriteLine( Watch.Roll().Ticks(10000).AllToString().AllJoin(", ") );

它将滚动直到 10,000 个滴答声过去,将所有内容转换为字符串并连接所有内容,同时保持方法链。这很重要,顺便说一句。我不能接受任何会破坏方法链的答案。请记住,这只是一个示例。
不,我无法对 Roll() 方法进行任何更改。该方法是只读的...

那么,我如何重写这个 Ticks() 方法使其更类似于 Ticks2() 方法而不最终聚合所有数据?


对于那些对 AllToString() 和 AllJoin 方法感兴趣的人:

    public static IEnumerable<string> AllToString<T>(this IEnumerable<T> data) => data.Select(item => item.ToString());
    public static string AllJoin<T>(this IEnumerable<T> data, string separator) => string.Join(separator, data.AllToString());

这两种方法对于您使用枚举并需要字符串输出的其他解决方案非常实用。

public static IEnumerable<T> Ticks<T>( this IEnumerable<T> source, Int64 totalTicks )
{
    Stopwatch sw = Stopwatch.StartNew();

    foreach( T item in source )
    {
        if( sw.ElapsedTicks < totalTicks )
        {
            yield return item;
        }
        else
        {
            yield break;
        }
    }
}

您实际上根本不需要任何自定义扩展方法:您也可以只使用 TakeWhile 代替 TicksSelect 而不是 AllToString(),以及Aggregate 而不是 AllJoin:

Stopwatch sw = Stopwatch.StartNew();

String all = Watch.Roll()
    .TakeWhile( _ => sw.ElapsedTicks < 10000 )
    .Select( i => i.ToString() )
    .Aggregate(
        new StringBuilder(),
        ( s, sb ) => sb.Append( s ).Append(", "),
        sb => sb.ToString()
    );

Console.WriteLine( all ):