任何弱的实习集合(对于不可变对象)

时间:2012-06-06 23:22:38

标签: .net immutability weak-references string-interning

在某些涉及不可变对象的情况下,许多不同的对象可能存在,这些对象在语义上是相同的。一个简单的例子是从文件中读取许多行文本到字符串。从程序的角度来看,两行具有相同字符序列的事实将是“巧合”,但从程序员的角度来看,可能会出现大量重复。如果许多字符串实例相同,将对这些不同实例的引用更改为对单个实例的引用将节省内存,并且还将促进它们之间的比较(如果两个字符串引用指向相同的字符串,则不需要执行字符 - 通过字符比较来确定它们是相同的。)

对于某些情况,系统提供的字符串实习设施可能很有用。然而,它有一些严重的限制:

  1. 一旦字符串被实习,该实习副本将永远存在,无论是否存在任何其他引用
  2. 字符串实习工具仅适用于字符串,不能与任何其他不可变类型一起使用。

如果存在真WeakDictionary<ImmutableClassType, ImmutableClassType>(对于每个元素,键和值都相同),代码可以执行以下操作:

if (theDict.TryGetValue(myString, ref internedString))
  myString = internedString;
else
  theDict[myString] = myString;

不幸的是,我不知道.net中的任何内置WeakDictionary<keyType, valType>类。此外,当两个引用始终指向同一个东西时,为每个项的键和值生成弱引用似乎是浪费。

我已经阅读了一些关于ConditionalWeakTable的内容,这听起来像一个有趣的类,但我认为它不会在这里使用,因为目标是能够采用一个实例并找到另一个独立的语义等价的实例。

对于类的实例具有明确定义的生命周期的情况,使用传统的Dictionary来合并相同的实例可能是合理的。但是,在许多情况下,可能很难知道什么时候应放弃这样的Dictionary或清除其中的物品。基于WeakReference的实习收集可以避免此类问题。这样的事情是存在的,还是可以很容易实现的?

附录的 正如svick所指出的那样,Dictionary<WeakReference, WeakReference>会有些问题,因为没有切实可行的方法来定义IEqualityComparer,其中WeakReference会返回其目标的GetHashCode值,并有一个死人继续返回该值。可以定义一个结构,它将包含一个整数target-hashcode值(在构造函数中设置),并且其自己的GetHashCode将返回该整数。稍微改进可能是使用ConditionalWeakTableWeakReference的目标链接到可终结的对象,该对象可用于将表项排入队列以进行删除。

我不确定尝试急切地清理字典与采用更加被动的方法之间的适当平衡(例如,如果自上次扫描后至少有一个GC,则在添加项目时执行扫描,并且自上次扫描以来添加的项目数超过了幸存的项目数)。扫描字典中的所有内容并不是免费的,但ConditionalWeakTable可能也不会是免费的。

PPS 的 我想到的另一个概念,但我认为它可能不像弱实用方法那样有用,就是让逻辑不可变类型保存一个可变的“时间戳”值,并且有一个接受它的比较方法ref的论点。如果发现两个不同的实例相等,则将检查它们的时间戳值。如果两者都为零,则将从全局计数器(-1,-2,-3等)中为它们分配连续的负数。具有(或被分配)较低时间戳值的参数将被另一个替换。

使用这种方法,如果反复比较许多对象实例,许多引用将被替换为对“较旧”实例的引用。根据使用模式,这可能导致大多数相同的对象实例被合并而不使用任何类型的实习字典。但是,对嵌套对象应用这种方法需要“不可变”对象允许嵌套对象引用变异以指向其他所谓相同的嵌套对象。如果“假设相同”的对象总是存在,那应该没问题,但如果不是这样的话,可能会造成相当奇怪的错误行为。

1 个答案:

答案 0 :(得分:2)

您可以使用自定义相等比较器创建类似Dictionary<WeakReference, WeakReference>的内容,并修剪那些在适当的时间不再存在的内容。与此相关的一个问题是如何从字典中删除死WeakRefrence,因为您无法通过引用相等(请记住,您必须使用自定义相等比较器)或索引来删除它。可能的解决方案是创建一个继承自WeakReference并且具有正确哈希码的类型,即使引用已经死亡。或者您可以将其包装在自定义struct

但正如你所说,如果每个引用在它死后立即从字典中删除,那将会很好。我认为这样做的唯一方法是以某种方式使用终结器。但是如果你不想(或不能)修改字典中的类型,这将变得非常复杂。

基本思想是你将拥有与上面相同的弱引用字典(关于如何删除项目仍然适用的注意事项),但是你还使用{{}将一个带有终结器的辅助对象附加到字典中的每个项目。 1}}。在该终结器中,您将从字典中删除该项目。由于ConditionalWeakTable如何工作,如果字典中的一个项目得到了GCed,附加的对象也将被调用,这意味着它的终结器将被调用,因此该项目将从字典中删除