您更喜欢“具体化”IEnumerables?

Ale*_*i S 2 c# linq memory performance functional-programming

有时有必要在方法中间实际“评估” 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.
            }
Run Code Online (Sandbox Code Playgroud)

我曾经做过:

var skippedIds = objects.Where(x => x.State=="skip")
                    .Select(x => x.Id)
                    .Distinct()
                    .ToList();
...
Run Code Online (Sandbox Code Playgroud)

但想知道是否有更好的选择。上面的代码List<T>在堆上创建对象,我猜这是在方法内死亡的临时变量的上下文中不必要的 GC 负担。我现在正在使用库ToImmutableArray()自带的System.Collections.Immutable这不仅创建了堆栈分配的对象不是真的,感谢评论者),而且还将“不可变”语义附加到我的代码中,我认为这是一个很好的函数式实践。

但对性能有什么影响呢?“具体化”在方法内本地多个位置使用的临时子查询结果的最佳方式是什么?

lou*_*ter 5

将其具体化到内存中对性能的影响是:

  • 最初从数据库中获取所有项目 - 如果您不打算使用所有项目,那么您可能会获取超出您需要的项目。
  • 根据您使用的结构,您可能会产生插入成本 -ToImmutableArray()大约会像ToArray()ImmutableArray包装内置数组类型并删除突变选项一样快。
  • 如果您快速丢弃对象,则 GC 负担就不那么重要了。因为该物品不太可能从 跳转Gen 0Gen 1并在没有太多成本的情况下被收集。但显然,分配的大对象越多,触发集合的可能性就越大。

您可以使用language-extSeq<A>中的类型(披露:我是作者)。它被设计为“更好的可枚举”,因为它只会一次性消耗每个项目,并且像.IEnumerable<A>IEnumerable<A>

所以,你可以这样做:

var skippedIds = objects.Where(x => x.State=="skip")
                        .Select(x => x.Id)
                        .Distinct()
                        .ToSeq();
Run Code Online (Sandbox Code Playgroud)

显然,这个世界上没有免费的东西,而代价是Seq<A>

  • 每个消耗的项目的分配(因为它会记住您已阅读的项目,这样您就不会再这样做)。但它们是微小的对象,只有两个引用,因此造成的 GC 压力很小。
  • 与数据库保持打开连接的时间比您可能需要的时间长,这可能会导致数据库出现其他性能问题:死锁等。

但好处是你只吃你需要的东西,而且只吃一次。就我个人而言,我希望限制您的查询和使用ToImmutableArray(),从数据库中获取少于您需要的数据将始终是首选方法。