从 ConcurrentDictionary 转换为 IDictionary

本文关键字:IDictionary 转换 ConcurrentDictionary | 更新日期: 2023-09-27 18:30:50

ConcurrentDictionaryIDictionary的强制转换是否会切断线程安全的实现,因为IDictionary没有GetOrAddAddOrUpdate的方法?

从 ConcurrentDictionary 转换为 IDictionary

生成的对象仍将是并发字典。像"添加"或"删除"这样的调用使用基础实现 TryAdd 和 TryMove(它们是线程安全的)。将对象强制转换为其他类型不会更改对象本身。

此外,为了澄清起见,您可以使用 ILSpy 等工具来查看默认 IDictionary 方法的实现是什么,以及它们是否仍然是线程安全的。

IDictionary只是一个接口。 如果强制转换为它,则结果是 ConcurrentDictionary 的实现,缺少 GetOrAddAddOrUpdate 方法。

据推测,您仍然可以使用 Item 属性以及 AddContainsKey 方法(代替 GetOrAddAddOrUpdate )方法,并且您的强制转换对象仍然是线程安全的(因为底层实现是ConcurrentDictionary)。

这就像通过IDictionary形状的钥匙孔看大ConcurrentDictionary物体 - 你只能看到IDictionary形状,但它仍然是ConcurrentDictionary

接口不会影响实现。它只是没有暴露ConcurrentDictionary的一些方法。

您可能会发现这或这有助于理解界面。

当一个ConcurrentDictionary<K,V>作为IDictionary<K,V>公开时,它在非常有限的意义上是线程安全的:其内部状态的一致性是有保证的。不会抛出未记录的异常,也不会突然出现从未添加到字典中的值。但从更广泛的意义上讲,考虑到字典如何与应用程序的其余部分交互以及它包含的数据的意义,它不太可能是线程安全的。原因是 IDictionary<K,V> 接口不公开使ConcurrentDictionary<K,V>适合多线程使用的专用原子 API。如果没有这些 API,许多常见操作就变得不可能。

例如,假设您有一个ConcurrentDictionary<string, int>,并且想要递增键的值"A",或者,如果键不存在,则将其与值1一起添加。要使用的正确 API 是 AddOrUpdate

dictionary.AddOrUpdate("A", 1, (_, existing) => existing + 1);

没有此 API,您将陷入困境。通过仅使用 IDictionary<string, int> 接口的成员,您可以做的最好的事情是:

if (dictionary.TryGetValue("A", out int existing))
    dictionary["A"] = existing + 1;
else
    dictionary.Add("A", 1);

。这是不可挽回的缺陷,因为操作不是原子的。另一个线程可能会在调用TryGetValue和设置索引器之间更改键的值。因此,键"A"的最终值可能不等于它(假定)递增的次数。无法修复此争用条件。IDictionary<K,V>接口根本不是为多线程使用而设计的。

简答题 否。

您正在通过接口操作对象,因此仍在使用具体实现。您不会丢失任何功能或其方法。它们只是不可用。

附带说明一下,在向下转换时需要显式转换,在向上转换时不需要显式转换 - 这样做始终是安全的。