有时有必要在方法中间实际“评估” IEnumerable,因为它在多个查询中使用并且编译器发出警告(“可能的IEnumerable多重枚举”)
var skippedIds = objects.Where(x => x.State=="skip")
.Select(x => x.Id)
.Distinct();
var skippedLookup = skippedIds.ToLookup(x => x.FundId, _ => new { _.Id, _.Name});
if (skippedIds.Any()) // compiler warning
{
...
// other iterations over skippedIds, etc.
}
我曾经做过:
var skippedIds = objects.Where(x => x.State=="skip")
.Select(x => x.Id)
.Distinct()
.ToList();
...
,但想知道是否有更好的选择。上面的代码在堆上创建List<T>
对象,我猜这是在方法中死亡的临时变量的上下文中不必要的GC负担。
我现在正在使用ToImmutableArray()
库随附的System.Collections.Immutable
。 不仅会创建堆栈分配的对象(不正确,谢谢注释),而且还会在我的代码中附加“不可变”语义,这是一种很好的功能风格练习。
但是性能影响是什么?在一个方法的本地多个地方“物化”临时子查询结果的首选方式是什么?
答案 0 :(得分:3)
在内存中实现它的性能含义是:
ToImmutableArray()
的费用大约是ToArray()
,因为ImmutableArray
仅包装内置数组类型并删除了突变选项。Gen 0
跳到Gen 1
的可能性很小,而且无需花费太多就可以收集。但是显然,您分配的对象越多,触发集合的可能性就越大。
您可以使用language-ext中的Seq<A>
类型(公开:我是作者)。之所以被设计为“更好的可枚举”,是因为它只会消耗一次IEnumerable<A>
中的每个项目,并且像IEnumerable<A>
一样懒惰。
因此,您可以这样做:
var skippedIds = objects.Where(x => x.State=="skip")
.Select(x => x.Id)
.Distinct()
.ToSeq();
显然,这个世界上没有免费的东西,Seq<A>
的成本是:
但是好处是您只消耗所需的东西,而只消耗一次。就我个人而言,我希望限制您的查询并使用ToImmutableArray()
,从数据库中获取少于您所需的资源将永远是首选方法。
答案 1 :(得分:0)
在这种情况下,问题是您已经实现了结果(以Lookup
的形式),但随后引用了未实现结果。
var skippedIds = objects.Where(x => x.State=="skip")
.Select(x => x.Id)
.Distinct();
var skippedLookup = skippedIds.ToLookup(x => x.FundId, _ => new { _.Id, _.Name});
if (skippedIds.Any()) // compiler warning
在上面的代码中,skippedIds
未实现,但skippedLookup
已实现。因此,您可以考虑更改:
if (skippedIds.Any()) // compiler warning
收件人:
if (skippedLookup.Any()) // no compiler warning
如果我们采用更一般的情况,则需要一些其他指导:
ToList
或ToImmutableArray
来实现(两者看起来都表现不错)。Any
然后使用foreach
-在许多情况下,Any
可以删除,因为如果foreach
会自动不执行任何操作,枚举为空。IEnumerable
使用LINQ to Objects并且您正在执行Distinct
,则执行实例化操作(例如ToList
),然后改为使用new HashSet<YourTypeHere>(YourEnumerableHere)
。一键执行Distinct
和物化操作。ToList
实现时,请考虑将生成的List
暴露为IReadOnlyList
,以向消费者表明它并非旨在更改。List
及其底层数组会产生一些GC开销。但是,在整体GC负载(例如List
所包含的对象)的更广泛上下文中,这不太可能成为问题。如果列表足够大,则可以使用大对象堆,这不是最佳选择。但老实说,让GC做好工作。如果有问题,请先优化,然后再优化。