首页 > 解决方案 > 如何将列表的子集添加到另一个列表中对象的属性 - 最佳/最快实践

问题描述

我试图找到最快和最高效的方法来根据关键属性从列表中选择项目的子集,并将这个子集(列表)分配给另一个列表中项目的属性。性能方面很重要,因为这部分代码将在每个工作日被非常频繁地调用。我以刻度测量了性能,以清楚地看到相对差异。

我有两个列表(示例设置);

List<CategorySetting> catList;
List<Customer> custList;

CategorySetting实体有一个名为 的属性SettingsId。该Customer实体也有一个SettingsId属性,它实际上是一个来自Customersto的外键CategorySetting

我写的第一段代码是最直接的;

// normal for each: 13275 ticks
foreach (var catItem in catList)
{
   catItem.Customers = custList.Where(c => c.SettingsId == catItem.SettingsId).ToList();
}

这将需要大约 13275 个滴答声。

然后我想也许使用并行性这可能会快很多?所以我写了这段代码;

// parallel for each: 82541 ticks
Parallel.ForEach(catList, catItem =>
{
   catItem.Customers = custList.Where(c => c.SettingsId == catItem.SettingsId).ToList();
});

这需要更长的时间。82541 滴答声。由于这种方法的并行性,这对我来说毫无意义。它应该使用多个线程来执行此操作,因此理论上应该更快。然后我开始想知道如果多个线程同时尝试访问客户列表会发生什么。这可能会导致锁和队列,因此由于开销而需要更多时间?与写入主列表相同。

我尝试了另一种方法。我ConcurrentBag为 catList(主列表)做了一个。

ConcurrentBag<CategorySettings> csBag = new ConcurrentBag<CategorySettings>(catList);

我加入的 custListConcurrentDictionary已经按SettingsId.

var dict = custList.GroupBy(c => c.SettingsId).ToDictionary(x => x.Key, y => y.ToList());
ConcurrentDictionary<int?, List<Customer>> concDict = new ConcurrentDictionary<int?, List<Customer>>(dict);

最后的尝试是这样的:

// paralell, bag, concurrent dictionary: 40255
Parallel.ForEach(caBag, ca =>
{   
   concDict.TryGetValue(ca.SettingsId, out var selCust);
   ca.Customers = selCust;
});

这需要 40255 个滴答声。谁能解释为什么这仍然需要更长的时间?更重要的是,除了“只是”一个 foreach 循环之外,没有其他方法了吗?感觉就像我在这里错过了一些东西。

任何想法都非常感谢!

标签: c#performancelinqparallel.foreachconcurrentdictionary

解决方案


您可以尝试使用ToLookupLINQ 方法:

var customersLookup = custList.ToLookup(item => item.SettingsId);

foreach (var catItem in catList)
{
    catItem.Customers = customersLookup[catItem.SettingsId].ToList();
}

我假设CategorySetting该类具有Customerstype的可写属性IList<Customer>。如果属性是 type IEnumerable<Customer>,您可以省略ToList调用。


推荐阅读