【问题标题】:How do I prevent the enumerator from aggregating all items?如何防止枚举器聚合所有项目?
【发布时间】:2020-02-21 06:47:44
【问题描述】:

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

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());

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

【问题讨论】:

  • 如果有人想知道:我想要遍历的枚举基本上是几个方法链接在一起的筛子。所以更像 A().B().C().D().Ticks(1000) 因为如果满足条件,每个方法都会产生下一个方法的值。有点难看,但它必须适合这个......
  • “但是后一个函数将失败,因为 GetEnumerator() 方法将聚合所有数据并在不定式枚举中,这需要永远......”这不是真的 - GetEnumerator() 不是遍历枚举器。
  • stackoverflow.com/users/159145/dai 原来你是对的。事实证明,与第一个相比,它是如此之快,以至于存储的列表非常庞大......
  • 顺便说一句,我希望您的 AllToStringAllJoin 方法使用单个 StringBuilder 实例而不是 s3 = s1 + s2 执行字符串连接,因为重复的字符串连接很昂贵。
  • public static IEnumerable AllToString(this IEnumerable data) => data.Select(item => item.ToString()); public static string AllJoin(this IEnumerable data, string separator) => string.Join(separator, data.AllToString());我认为他们确实... :)

标签: .net-core c#-6.0 .net-4.8


【解决方案1】:
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 ):

【讨论】:

  • AllToString() 和 AllJoin() 不是很复杂的方法。 TakeWhile 是一个不错的选择,但需要在方法链之外定义秒表。我的 Ticks() 方法将它保存在方法链中,并在真正的枚举开始后实际开始测量。调用 Roll() 也需要很短的时间,我的 Ticks() 不会计算在内。
猜你喜欢
  • 1970-01-01
  • 2016-10-12
  • 2016-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-05
相关资源
最近更新 更多