如何使ToLookup()
并发?我有一些这样的代码:
myRepository.GetAllContacts().ToLookup( c => c.COMPANY_ID);
我想要一个类似于以下的结构:
new ConcurrentDicitonary<String,IEnumerable<Contact>>(); // <Company,List of Contacts in company>
因为每个
COMPANY_ID
可以映射到多个Contact
,所以我不能简单地使用ToDictionary
。
最佳答案
这似乎是一个简单的问题,但是正如其他(有问题的)答案所表明的那样,该解决方案并非真正简单。
现有答案存在的问题
就目前而言,这两个提议的解决方案都会导致创建某种字典,在该字典中,每次您在任何给定键处枚举IEnumerable<Contact>
时,都会通过枚举和过滤原始集合从头重新创建已过滤的IEnumerable<Contact>
。本质上,您在字典中存储的是获取所需过滤的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>>
作为字典类型(您可以在上面的示例中替换字典类型,并且仍然可以按预期方式编译和运行)-只需确保仅将具体化的集合(最好是不可变的集合)添加到字典中即可在构建它时。选择
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>
仍然适用。
关于c# - 并发的ToLookup()转换?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34687284/