如何让ToLookup()
并发?我有一些像这样的代码:
myRepository.GetAllContacts().ToLookup( c => c.COMPANY_ID);
我想要一个类似的结构:
new ConcurrentDicitonary<String,IEnumerable<Contact>>(); // <Company,List of Contacts in company>
由于每个COMPANY_ID
可以映射到多个Contact
,因此我无法使用ToDictionary
。
答案 0 :(得分:4)
这似乎是一个简单的问题,但正如其他(有问题的)答案所显示的那样,解决方案并非真正无足轻重。
现有答案的问题
就目前而言,两个提出的解决方案都会导致创建某种字典,每次在任何给定键上枚举IEnumerable<Contact>
时,通过枚举和过滤从头开始重新创建过滤的IEnumerable<Contact>
原来的收藏品。基本上,您在字典中存储的是 logic 以获取所需的已过滤的Contact
集合,而不是实际的集合。
因此,您将一遍又一遍地枚举原始IEnumerable<Contact>
。从线程安全的角度来看这是危险的,即使它起作用 - 这样做也没有任何好处,只有开销。
建议的解决方案
你是对的,Lookup/ILookup<TKey, TValue>
的最佳开箱即用线程安全替代方案似乎是ConcurrentDictionary<TKey, TValue>
,其中TValue
来自IEnumerable<Contact>
。它提供了查找功能的超集和是正确构建它的 。在基类库中没有现成的扩展方法,因此您可以自己编写实现:
IEnumerable<Contact> contacts = GetAllContacts();
ConcurrentDictionary<string, IReadOnlyList<Contact>> dict = new ConcurrentDictionary<string, IReadOnlyList<Contact>>();
foreach (IGrouping<string, Contact> group in contacts.GroupBy(c => c.COMPANY_ID))
{
if (!dict.TryAdd(group.Key, group.ToArray())) {
throw new InvalidOperationException("Key already added.");
}
}
这与其他人提供的内容非常相似,但有一个重要区别:我的词典TValue
是具体化集合(特别是Contact[]
冒充IReadOnlyList<Contact>
})。每次将它从字典中拉出并枚举它时,都不会从头开始重建。
哦,而且我也只列举过一次IEnumerable<Contact>
来源 - 不是真正改变生活,而是一种不错的感觉。
您仍然可以使用ConcurrentDictionary<string, IEnumerable<Contact>>
作为您的字典类型(您可以在上面的示例中替换字典类型,它仍然可以按预期编译和工作) - 只需确保您只添加实体化< / em>,最好是在构建字典时将不可变的集合添加到字典中。
选择TValue
类型:IReadOnlyList<T>
的替代
(超出原始问题的范围)
IReadOnlyList<T>
是我能想到的最通用的通用准不可变集合接口(显然除IReadOnlyCollection<T>
之外),它向调用者传达集合已实现且不太可能在将来
如果我在我自己的代码中使用它,我实际上会使用Contact[]
作为我的字典TValue
用于任何私人和内部呼叫(出于性能原因放弃“只读”的舒适度)。对于任何公共API,我会坚持IReadOnlyList<T>
或可能ReadOnlyCollection<T>
来强调TValue
集合的只读方面。
如果采用外部依赖是一个可行的选项,您还可以将Microsoft的System.Collections.Immutable
NuGet添加到项目中,并使用ImmutableDictionary<string, ImmutableArray<Contact>>
来存储查找。 ImmutableDictionary<TKey, TValue>
是一个不可变的线程安全字典。 ImmutableArray<T>
是一个轻量级数组包装器,具有很强的不变性保证,并且还具有通过结构枚举器实现的可靠性能特性,以及某些LINQ方法的重新实现,这些方法完全避免了枚举器分配。
List<T>
对于TValue
来说是一个糟糕的选择,因为它是a)可变性和b)它倾向于分配长度大于List<T>.Count
的内部缓冲区数组(除非你明确使用List<T>.TrimExcess
)。当你把东西存放在字典中时,很有可能它会活一段时间,所以分配你不会真正使用的内存(如List<T>
那样)并不是一个好主意。
修改的
现在,我必须补充一点:LINQ Lookup<Tkey, TValue>
返回的.NET当前ToLookup
实现似乎是线程安全的。但是,我发现的所有规范都没有对Lookup<TKey, TValue>
上的实例方法的线程安全做出任何保证(MSDN特别指出它们不能保证是线程安全的),这意味着查找线程安全性是实施细节,而不是防弹保证。因此我上面所说的所有使用ConcurrentDictionary<TKey, TValue>
仍然适用。