ReadOnlyDictionary和ImmutableDictionary有什么区别?

58
在C#中,这两个容器在特性或使用情况方面有什么关键区别?在Google上似乎没有任何比较它们的信息。

System.Collections.ObjectModel.ReadOnlyDictionary System.Collections.Immutable.ImmutableDictionary

我知道ImmutableDictionary是线程安全的。那么ReadOnlyDictionary也是吗?

这不是如何正确使用IReadOnlyDictionary?的重复。那个问题是关于如何使用IReadOnlyDictionary。这个问题是关于两者之间的区别(正如某人在2015年的评论中指出的那样,这将是一个不同的问题 - 也就是这个问题)。


3
你只能从ReadOnlyDictionary中读取数据。ImmutableDictionary允许你执行添加和清除等操作,但它不会改变原有的字典,而是创建一个包含更改的新字典。 - juharr
就像juharr所说的那样。此外,为了进一步解释,只读集合之所以始终是线程安全的,是因为您无法更新其中的值,也无法添加新值进去。 - Dan Rayson
1
{btsdaf} - fharreau
这个回答解决了你的问题吗?如何正确使用IReadOnlyDictionary? - Jim G.
不好意思,它并没有回答这个问题。那个问题是关于如何使用IReadOnlyDictionary的。而这个问题是关于两者之间的区别(正如2015年那个帖子中有人评论的那样,这将是一个不同的问题 - 也就是这个问题)。 - matt_rule
5个回答

76
  • 一个 ReadOnlyDictionary 只能通过构造函数初始化一次,然后你就无法向其中添加或删除项(它们会抛出 NotSupportedException)。如果你想确保在应用程序的多个层之间发送时不会被修改,那么它非常有用。
  • 一个 ImmutableDictionary 有像 AddRemove 这样的修改方法,但它们将创建一个新的字典并返回,原始字典保持不变,返回新的不可变字典的副本。

请注意:

  • 通过将另一个字典实例传递给 构造函数 来初始化 ReadOnlyDictionary。这解释了为什么 ReadOnlyDictionary 是可变的(如果底层字典被修改)。它只是一个受到直接更改保护的包装器。
  • 你不能使用构造函数来创建 ImmutableDictionary我如何创建一个新的 ImmutableDictionary 实例?
那也解释了为什么ReadOnlyDictionary不是线程安全的(更好的说法:它和底层字典一样线程安全)。ImmutableDictionary是线程安全的,因为您无法修改原始实例(既不能直接修改也不能间接修改)。所有“修改”它的方法实际上都会返回一个新实例。但是,如果您需要一个线程安全的字典,并且不需要它是不可变的,请改用ConcurrentDictionary

1
我们将在Net8中使用FrozenDictionary,它应该比ImmutableDictionary读取更快速。 - undefined

10
一个ReadOnlyDictionary<TKey,TValue>是一个包装另一个现有的实现了IDictionary<TKey,TValue>的对象。
重要的是,虽然“你”(能够访问ReadOnlyDictionary的代码)不能通过包装器对字典进行任何更改,但这并不意味着其他代码无法修改基础字典。
因此,与其他答案可能暗示的不同,您不能假设ReadOnlyDictionary不受修改的影响-只是"你"没有被允许。例如,您不能确定两次尝试访问特定键会产生相同的结果。

3
除了现有的答案,我想补充一点,即ImmutableDictionary较慢,通常会使用更多的内存。
  • 为什么较慢?在幕后,ImmutableDictionary不是一个哈希表。它使用的是AVL树,这是一种自平衡树,因此,其访问复杂度为O(logn)。另一方面,其他字典使用幕后的哈希表,它们的访问复杂度为O(1)
  • 为什么需要更多的内存分配?每次更改字典时,它都会创建一个新的字典,因为它是不可变的。

2
每次更改字典时,因为它是不可变的,所以会创建一个新字典。实际上发生的是创建了一个新节点,并连接到现有二叉树的节点上。可能需要创建一些额外的节点来平衡树。并不是在每次添加或删除后从头开始重新创建整棵树。大部分现有的结构得以保留。 - Theodor Zoulias

0

与其描述这两个类的作用,倒不如更好地描述什么是只读或不可变的意义,因为这存在一个重要区别,这两种实现并没有太多选择。

只读是一个类的“接口”,即其公共方法和属性集合的一部分。 只读意味着外部使用类的消费者无法采取任何可能影响其可见状态的行动序列。例如,以只读文件为比较对象;没有应用程序可以使用相同的API将数据写入此类文件,因为该API使文件为只读。

只读是否意味着线程安全? 不一定-只读类仍然可以使用缓存或优化其内部数据结构等内容,而这些内容在并发调用时可能会出现问题。

只读是否意味着永不更改?也不是,例如系统时钟。您无法真正影响它(默认权限下),只能读取它(根据定义使其只读),但其值基于时间而变化。

不变意味着不可变。这是一个更强的概念,就像线程安全一样,它是整个类的契约的一部分。该类必须积极确保其实例在其生命周期内不会发生任何变化,至于外部观察到的内容。

.NET中的字符串是不可变的:只要运行时的完整性没有受到破坏(通过内存黑客),字符串的特定实例将永远不会与其最初观察到的值不同。另一方面,只读文件并不是非常不可变的,因为可以随时关闭只读并更改文件。

不可变也不意味着线程安全,因为这样的对象仍然可能使用修改其内部状态的技术,并且不是线程安全的(但通常更容易确保)。

不可变是否意味着只读取决于您如何看待它。通常,您可以以不影响可能正在使用它的外部代码的方式“突变”不可变对象,因此公开不可变对象至少与公开只读对象一样强大。从字符串中获取子字符串就像安全地删除其中一部分。


这让我们回到了关于这两个类的最初问题。所有ReadOnlyDictionary需要做的就是只读。你仍然需要以某种方式提供数据,使用内部包装的字典,只有你自己才能通过内部字典进行写入。包装器提供了“强”只读访问(与仅通过转换为IReadOnlyDictionary获得的“弱”只读访问相比)。它也是线程安全的,但前提是底层字典也是线程安全的。 ImmutableDictionary可以使用其持有的数据不可更改的强保证做更多的事情。实质上,您可以使用新数据“修补”其中的一部分,并获得修改后的“副本”结构,但实际上并没有复制完整的对象。它也是由其实现方式保证线程安全的。类似于StringBuilder,您可以使用构建器对实例进行更改,然后将其烘焙以生成不可变字典的最终实例。

-2

ReadOnlyDictionary: 是只读的,不能添加或删除

ImmutableDictonary: 可以添加或删除,但它像字符串一样是不可变的。需要创建新的对象来添加和删除。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接