C# 并发ToLookup()转换?

C# 并发ToLookup()转换?,c#,.net,parallel-processing,concurrentdictionary,C#,.net,Parallel Processing,Concurrentdictionary,如何使ToLookup()并发?我有一个类似这样的代码: myRepository.GetAllContacts().ToLookup( c => c.COMPANY_ID); 我希望有一个类似的结构: new ConcurrentDicitonary<String,IEnumerable<Contact>>(); // <Company,List of Contacts in company> newconcurrentdicitory();//

如何使
ToLookup()
并发?我有一个类似这样的代码:

myRepository.GetAllContacts().ToLookup( c => c.COMPANY_ID);
我希望有一个类似的结构:

new ConcurrentDicitonary<String,IEnumerable<Contact>>();  // <Company,List of Contacts in company>
newconcurrentdicitory();//

因为每个
公司ID
都可以映射到多个
联系人
,所以我不能简单地使用
进行编辑

,这似乎是一个简单的问题,但正如其他(有问题的)答案所示,解决方案其实并不简单

现有答案的问题

目前,这两种建议的解决方案都会导致创建某种字典,其中每次在任何给定键处枚举
IEnumerable
,都会通过枚举和过滤原始集合从头开始重新创建过滤后的
IEnumerable
。本质上,您在字典中存储的是获取所需的过滤
联系人
集合的逻辑,而不是实际集合

因此,您将反复枚举原始的
IEnumerable
。从线程安全的角度来看,这是危险的,即使它有效——这样做没有好处,只有开销

建议的解决方案

您是对的,
Lookup/ILookup
的最佳现成线程安全替代方案似乎是
ConcurrentDictionary
,其中
TValue
源自
IEnumerable
。它提供了查找功能的超集,如果您正确构建它,它是线程安全的。基类库中没有用于此的现成扩展方法,因此您只需滚动自己的实现:

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.");
    }
}
IEnumerable contacts=GetAllContacts();
ConcurrentDictionary dict=新建ConcurrentDictionary();
foreach(在contacts.GroupBy(c=>c.COMPANY\u ID)中对组进行分组)
{
如果(!dict.TryAdd(group.Key,group.ToArray())){
抛出新的InvalidOperationException(“已添加密钥”);
}
}
这看起来与其他人提供的非常相似,但有一个重要的区别:我的字典的
TValue
是一个具体的集合(特别是
Contact[]
冒充
IReadOnlyList
)。它不会在每次从字典中取出并枚举它时从头开始重建

哦,而且我只列举了一次源代码
IEnumerable
,从来都没有——不是真正改变生活,而是一次很好的接触

您仍然可以使用
ConcurrentDictionary
作为您的字典类型(您可以在上面的示例中替换字典类型,它仍将按照预期编译和工作)-只需确保在构建字典时只向字典添加具体化的集合,最好是不可变的集合

选择您的
t值
类型:IReadOnlyList的备选方案

(超出原问题的范围)

IReadOnlyList
是我能想到的最通用的准不可变集合接口(显然除了
IReadOnlyCollection
之外),它向调用者传达集合已经实现,将来不太可能更改

如果我在自己的代码中使用它,我实际上会使用
Contact[]
作为字典的
TValue
用于任何私人和内部调用(出于性能原因放弃“只读”的舒适性)。对于任何公共API,我都会坚持使用
IReadOnlyList
或可能的
ReadOnlyCollection
,以强调
TValue
集合的只读方面

如果采用外部依赖关系是一种可行的选择,您还可以将Microsoft的
System.Collections.Immutable
NuGet添加到您的项目中,并使用
ImmutableDictionary
存储查找
ImmutableDictionary
是一个不可变的线程安全字典
ImmutableArray
是一种轻量级数组包装器,它具有强大的不变性保证,并且通过结构枚举器和某些LINQ方法的重新实现(这些方法完全避免了枚举器分配)实现了可靠的性能特征

List
对于
TValue
来说是一个糟糕的选择,因为它是a)可变的,b)它倾向于分配长度大于
List.Count
(除非您明确使用
List.trimOverse
)。当你把东西藏在字典里时,它很有可能会存活一段时间,所以分配你不打算使用的内存(比如
List
does)不是一个好主意

编辑


现在,在所有这些之后,我必须添加:.NET当前的
查找
实现由LINQ的
ToLookup
返回,实际上看起来是线程安全的。但是,我发现的规范中没有一个对
查找
上的实例方法的线程安全性做出任何保证(MSDN特别声明它们不保证线程安全),这意味着查找线程安全性是一个实现细节,而不是防弹保证。因此,我上面所说的重新使用ConcurrentDictionary仍然适用。

这似乎是一个简单的问题,但正如其他(有问题的)答案所显示的那样,解决方案并非微不足道

现有答案的问题

目前,这两种建议的解决方案都会导致创建某种字典,其中每次在任何给定键处枚举
IEnumerable
,都会通过枚举和过滤原始集合从头开始重新创建过滤后的
IEnumerable
。本质上,您在字典中存储的是获取所需的过滤
联系人
集合的逻辑,而不是实际集合

因此,您将反复枚举原始的
IEnumerable
。从线程安全的角度来看,这是危险的,即使它有效,也没有b