多次读取 IEnumerable

发布于 2024-12-07 13:35:07 字数 412 浏览 4 评论 0原文

假设我有一些代码:

var items = ItemsGetter.GetAllItems().Where(x => x.SomeProperty > 20);
int sum1 = items.Sum(x => x.SomeFlag == true);

例如,我需要代码后面的项目集合中的一些其他总和。

int sum2 = items.Sum(x => x.OtherFlag == false);

所以我的问题是:可以多次调用 IEnumerable 上的 Linq 方法吗?也许我应该在枚举器上调用 Reset() 方法或使用 ToList 方法从项目中创建列表?

Let's say I have some code:

var items = ItemsGetter.GetAllItems().Where(x => x.SomeProperty > 20);
int sum1 = items.Sum(x => x.SomeFlag == true);

And for example I need some other sum from the items collection later in the code.

int sum2 = items.Sum(x => x.OtherFlag == false);

So my question: Is it OK to call Linq methods on IEnumerable more than once? Maybe I should call Reset() method on enumerator or make list from items using ToList method?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

心房敞 2024-12-14 13:35:07

嗯,这真的取决于你想做什么。您可以执行两次查询(其确切含义取决于 GetAllItems() 的作用),或者您可以复制结果到列表:

var items = ItemsGetter.GetAllItems().Where(x => x.SomeProperty > 20).ToList();

一旦它进入列表,显然多次迭代该列表就不是问题了。

请注意,您无法调用 Reset,因为您没有迭代器 - 您有 IEnumerable。无论如何,我不建议一般调用 IEnumerator - 许多实现(包括 C# 编译器从迭代器块生成的任何实现)实际上并不实现 Reset (即他们抛出异常)。

Well, it really depends what you want to do. You could take the hit of executing the query twice (and the exact meaning of that will depend on what GetAllItems() does), or you could take the hit of copying the results to a list:

var items = ItemsGetter.GetAllItems().Where(x => x.SomeProperty > 20).ToList();

Once it's in a list, obviously it's not a problem to iterate over that list multiple times.

Note that you can't call Reset because you don't have the iterator - you have the IEnumerable<T>. I wouldn't recommend calling IEnumerator<T> in general anyway - many implementations (including any generated by the C# compiler from iterator blocks) don't actually implement Reset anyway (i.e. they throw an exception).

萌能量女王 2024-12-14 13:35:07

我偶尔会遇到必须多次处理可枚举的情况。如果枚举成本高昂、不可重复并且会产生大量数据(如从数据库读取的 IQueryable),则不能选择多次枚举,也不能将结果缓冲在内存中。

直到今天,我经常最终编写聚合器类,我可以将项目推送到 foreach 循环中,并最终读出结果 - 远不如 LINQ 优雅。

但是等等,我刚刚说了“推”吗?这听起来不像是……反应性的吗?所以我在今晚的散步中思考着。回到家我尝试了一下——而且很有效!

该示例片段展示了如何使用标准 LINQ 运算符(即 Rx 的运算符)在一次传递中从整数序列中获取最小和最大项:

public static MinMax GetMinMax(IEnumerable<int> source)
{
    // convert source to an observable that does not enumerate (yet) when subscribed to
    var connectable = source.ToObservable(Scheduler.Immediate).Publish();

    // set up multiple consumers
    var minimum = connectable.Min();
    var maximum = connectable.Max();

    // combine into final result
    var final = minimum.CombineLatest(maximum, (min, max) => new MinMax { Min = min, Max = max });

    // make final subscribe to consumers, which in turn subscribe to the connectable observable
    var resultAsync = final.GetAwaiter();

    // now that everybody is listening, enumerate!
    connectable.Connect();

    // result available now
    return resultAsync.GetResult();
}

I'm occasionally in the situation that I have to process an enumerable multiple times. If enumerating is expensive, non-repeatable and yields a lot of data (like a IQueryable that reads from a database), enumerating multiple times is not an option, neither is buffering the result in memory.

Until today I often ended up writing aggregator classes into which I could push items in a foreach loop and eventually read the results out - much less elegant than LINQ is.

But wait, did I just say "push"? Doesn't that sound like... reactive? So I was thinking during tonight's walk. Back home I tried it - and it works!

The example snippet shows how to get both the minimum and maximum items from a sequence of integers in a single pass, using standard LINQ operators (those of Rx, that is):

public static MinMax GetMinMax(IEnumerable<int> source)
{
    // convert source to an observable that does not enumerate (yet) when subscribed to
    var connectable = source.ToObservable(Scheduler.Immediate).Publish();

    // set up multiple consumers
    var minimum = connectable.Min();
    var maximum = connectable.Max();

    // combine into final result
    var final = minimum.CombineLatest(maximum, (min, max) => new MinMax { Min = min, Max = max });

    // make final subscribe to consumers, which in turn subscribe to the connectable observable
    var resultAsync = final.GetAwaiter();

    // now that everybody is listening, enumerate!
    connectable.Connect();

    // result available now
    return resultAsync.GetResult();
}
眼泪淡了忧伤 2024-12-14 13:35:07

LINQ 使用延迟执行,因此仅当您通过其他方法请求时才会枚举“items”。每个 Sum 方法都需要 O(n) 进行迭代。根据您的项目列表有多大,您可能不想多次迭代它。

LINQ uses deferred execution, so 'items' will only enumerate when you request it to via another method. Each of your Sum methods will take O(n) to iterate through. Depending on how large your items list is, you may not want to iterate over it multiple times.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文