并发 ToLookup() 转换?
Concurrent ToLookup() conversion?
如何使 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>
都会通过枚举从头开始重新创建并过滤原始集合。本质上,您在字典中存储的是 logic 以获得所需的过滤 Contact
集合,而不是 actual 集合。
因此,您将一遍又一遍地列举原始 IEnumerable<Contact>
。从 thread-safety 的角度来看,这是危险的,即使它有效 - 这样做没有任何好处,只有开销。
建议的解决方案
你说得对,out-of-the-box thread-safe Lookup/ILookup<TKey, TValue>
的最佳选择似乎是 ConcurrentDictionary<TKey, TValue>
,其中 TValue
来自 IEnumerable<Contact>
.它提供查找功能的超集 并且 是 thread-safe 如果您正确构建它。在基础 Class 库中没有 ready-to-use 扩展方法,所以你可以自己实现:
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
是一个 materialised 集合(特别是 Contact[]
冒充如 IReadOnlyList<Contact>
)。每次将其从字典中拉出并枚举时,它不会从头开始重建。
哦,而且我只列举来源 IEnumerable<Contact>
一次,永远 - 不是真的 life-changing,但很贴心。
你仍然可以使用 ConcurrentDictionary<string, IEnumerable<Contact>>
作为你的字典类型(你可以替换我上面例子中的字典类型,它仍然会按预期编译和工作) - 只要确保你只添加 materialised,最好是在构建字典时将不可变集合添加到字典中。
选择您的 TValue
类型:IReadOnlyList<T>
的替代品
(超出原题范围)
IReadOnlyList<T>
是我能想到的最通用的 general-purpose quasi-immutable 集合接口(显然 IReadOnlyCollection<T>
除外),它向调用者传达集合已实现并且将来不太可能改变。
如果我在自己的代码中使用它,我实际上会使用 Contact[]
作为我字典的 TValue
进行任何私人和内部调用(出于性能原因放弃 "read-only" 的舒适性).对于任何 public API,我会坚持使用 IReadOnlyList<T>
或 ReadOnlyCollection<T>
来强调 TValue
集合的 read-only 方面。
如果采用外部依赖项是一个可行的选择,您还可以将 Microsoft 的 System.Collections.Immutable
NuGet 添加到您的项目并使用 ImmutableDictionary<string, ImmutableArray<Contact>>
来存储您的查找。 ImmutableDictionary<TKey, TValue>
是一个不可变的 thread-safe 字典。 ImmutableArray<T>
是一个轻量级数组包装器,它具有强大的不变性保证,并且还具有通过结构枚举器和 re-implementations 某些完全避免枚举器分配的某些 LINQ 方法实现的可靠性能特征。
List<T>
对于 TValue
来说是一个糟糕的选择,因为它 a) 可变性和 b) 它倾向于分配长度大于 List<T>.Count
的内部缓冲区数组(除非你明确使用 List<T>.TrimExcess
)。当您将内容存储在字典中时,它很可能会存活一段时间,因此分配您不会真正使用的内存(就像 List<T>
那样)并不是一个好主意。
编辑
现在我必须添加:.NET 当前的 Lookup<Tkey, TValue>
实现由 LINQ 的 ToLookup
返回 实际上看起来是 thread-safe .但是,我发现的 none 规范对 Lookup<TKey, TValue>
上实例方法的 thread-safety 做出了任何保证(MSDN 特别声明它们不能保证是 thread-safe),这意味着 lookup thread-safety 是一个实现细节,而不是万无一失的保证。因此,我上面所说的关于使用 ConcurrentDictionary<TKey, TValue>
的所有内容仍然适用。
如何使 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>
都会通过枚举从头开始重新创建并过滤原始集合。本质上,您在字典中存储的是 logic 以获得所需的过滤 Contact
集合,而不是 actual 集合。
因此,您将一遍又一遍地列举原始 IEnumerable<Contact>
。从 thread-safety 的角度来看,这是危险的,即使它有效 - 这样做没有任何好处,只有开销。
建议的解决方案
你说得对,out-of-the-box thread-safe Lookup/ILookup<TKey, TValue>
的最佳选择似乎是 ConcurrentDictionary<TKey, TValue>
,其中 TValue
来自 IEnumerable<Contact>
.它提供查找功能的超集 并且 是 thread-safe 如果您正确构建它。在基础 Class 库中没有 ready-to-use 扩展方法,所以你可以自己实现:
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
是一个 materialised 集合(特别是 Contact[]
冒充如 IReadOnlyList<Contact>
)。每次将其从字典中拉出并枚举时,它不会从头开始重建。
哦,而且我只列举来源 IEnumerable<Contact>
一次,永远 - 不是真的 life-changing,但很贴心。
你仍然可以使用 ConcurrentDictionary<string, IEnumerable<Contact>>
作为你的字典类型(你可以替换我上面例子中的字典类型,它仍然会按预期编译和工作) - 只要确保你只添加 materialised,最好是在构建字典时将不可变集合添加到字典中。
选择您的 TValue
类型:IReadOnlyList<T>
(超出原题范围)
IReadOnlyList<T>
是我能想到的最通用的 general-purpose quasi-immutable 集合接口(显然 IReadOnlyCollection<T>
除外),它向调用者传达集合已实现并且将来不太可能改变。
如果我在自己的代码中使用它,我实际上会使用 Contact[]
作为我字典的 TValue
进行任何私人和内部调用(出于性能原因放弃 "read-only" 的舒适性).对于任何 public API,我会坚持使用 IReadOnlyList<T>
或 ReadOnlyCollection<T>
来强调 TValue
集合的 read-only 方面。
如果采用外部依赖项是一个可行的选择,您还可以将 Microsoft 的 System.Collections.Immutable
NuGet 添加到您的项目并使用 ImmutableDictionary<string, ImmutableArray<Contact>>
来存储您的查找。 ImmutableDictionary<TKey, TValue>
是一个不可变的 thread-safe 字典。 ImmutableArray<T>
是一个轻量级数组包装器,它具有强大的不变性保证,并且还具有通过结构枚举器和 re-implementations 某些完全避免枚举器分配的某些 LINQ 方法实现的可靠性能特征。
List<T>
对于 TValue
来说是一个糟糕的选择,因为它 a) 可变性和 b) 它倾向于分配长度大于 List<T>.Count
的内部缓冲区数组(除非你明确使用 List<T>.TrimExcess
)。当您将内容存储在字典中时,它很可能会存活一段时间,因此分配您不会真正使用的内存(就像 List<T>
那样)并不是一个好主意。
编辑
现在我必须添加:.NET 当前的 Lookup<Tkey, TValue>
实现由 LINQ 的 ToLookup
返回 实际上看起来是 thread-safe .但是,我发现的 none 规范对 Lookup<TKey, TValue>
上实例方法的 thread-safety 做出了任何保证(MSDN 特别声明它们不能保证是 thread-safe),这意味着 lookup thread-safety 是一个实现细节,而不是万无一失的保证。因此,我上面所说的关于使用 ConcurrentDictionary<TKey, TValue>
的所有内容仍然适用。