我在这里提供的信息不是新的,为了完整性我只是添加了此信息。
这段代码的想法很简单:
- 对象需要唯一的ID,默认情况下不存在。相反,我们必须依靠下一个最好的方法,那就是
RuntimeHelpers.GetHashCode
为我们提供一种唯一的ID
- 要检查唯一性,这意味着我们需要使用
object.ReferenceEquals
- 但是,我们仍然希望有一个唯一的ID,因此我添加了一个
GUID
,根据定义,它是唯一的。
- 因为我不喜欢在不需要时锁定所有内容,所以我不使用
ConditionalWeakTable
。
结合起来,将为您提供以下代码:
public class UniqueIdMapper
{
private class ObjectEqualityComparer : IEqualityComparer<object>
{
public bool Equals(object x, object y)
{
return object.ReferenceEquals(x, y);
}
public int GetHashCode(object obj)
{
return RuntimeHelpers.GetHashCode(obj);
}
}
private Dictionary<object, Guid> dict = new Dictionary<object, Guid>(new ObjectEqualityComparer());
public Guid GetUniqueId(object o)
{
Guid id;
if (!dict.TryGetValue(o, out id))
{
id = Guid.NewGuid();
dict.Add(o, id);
}
return id;
}
}
要使用它,请创建的实例,UniqueIdMapper
并使用GUID返回的对象。
附录
因此,这里还有更多事情要做。让我写下一点ConditionalWeakTable
。
ConditionalWeakTable
做几件事。最重要的是,它不关心垃圾收集器,即:无论如何,都将收集您在此表中引用的对象。如果您查找对象,则它基本上与上面的词典相同。
好奇不?毕竟,当GC收集一个对象时,它会检查是否存在对该对象的引用,如果存在,则会收集它们。因此,如果中有一个对象ConditionalWeakTable
,为什么要收集引用的对象呢?
ConditionalWeakTable
使用一个小技巧,其他.NET结构也使用此小技巧:实际上,它存储IntPtr而不是存储对对象的引用。由于不是真正的参考,因此可以收集对象。
因此,目前有两个问题要解决。首先,对象可以在堆上移动,那么我们将IntPtr用作什么呢?其次,我们如何知道对象具有有效的引用?
- 可以将对象固定在堆上,并可以存储其实际指针。当GC击中要移除的对象时,它将取消固定并收集它。但是,这意味着我们将获得固定的资源,如果您有很多对象,这不是一个好主意(由于内存碎片问题)。这可能不是它的工作方式。
- GC移动对象时,它会回调,然后可以更新引用。从外部调用的角度来看,这可能就是实现它的方式
DependentHandle
-但我认为它稍微复杂一些。
- 不是指向对象本身的指针,而是存储GC中所有对象列表中的指针。IntPtr是此列表中的索引或指针。仅当对象更改世代时,列表才会更改,此时简单的回调可以更新指针。如果您还记得Mark&Sweep的工作原理,那就更有意义了。没有固定,删除操作与以前一样。我相信这就是它的工作原理
DependentHandle
。
最后一种解决方案确实要求运行时在显式释放它们之前不要重新使用列表存储桶,并且还要求通过对运行时的调用来检索所有对象。
如果我们假设他们使用此解决方案,我们还可以解决第二个问题。Mark&Sweep算法可跟踪收集到的对象。我们已经知道了这一点。一旦对象检查对象是否存在,它将调用“ Free”,这将删除指针和列表项。该对象真的消失了。
此时要注意的重要一件事是,如果ConditionalWeakTable
在多个线程中进行更新并且如果它不是线程安全的,那么事情将发生严重错误。结果将是内存泄漏。这就是为什么所有来电ConditionalWeakTable
都进行简单的“锁定”的原因,以确保不会发生这种情况。
需要注意的另一件事是清理条目必须不时进行。尽管实际对象将由GC清除,但条目不是。这就是为什么ConditionalWeakTable
尺寸只会增大的原因。一旦达到某个限制(由哈希中的碰撞机会确定),它将触发a Resize
,该操作检查是否必须清理对象-如果需要清理,free
则在GC流程中调用该对象,并删除该IntPtr
句柄。
我相信这也是为什么DependentHandle
不直接公开的原因-您不想弄乱事物并因此而导致内存泄漏。接下来的最好的事情是WeakReference
(它还存储了一个IntPtr
代替对象的对象)-但不幸的是不包括“依赖”方面。
剩下的就是您可以熟练地研究机械原理,以便您可以看到实际的依赖关系。确保多次启动它并观察结果:
class DependentObject
{
public class MyKey : IDisposable
{
public MyKey(bool iskey)
{
this.iskey = iskey;
}
private bool disposed = false;
private bool iskey;
public void Dispose()
{
if (!disposed)
{
disposed = true;
Console.WriteLine("Cleanup {0}", iskey);
}
}
~MyKey()
{
Dispose();
}
}
static void Main(string[] args)
{
var dep = new MyKey(true); // also try passing this to cwt.Add
ConditionalWeakTable<MyKey, MyKey> cwt = new ConditionalWeakTable<MyKey, MyKey>();
cwt.Add(new MyKey(true), dep); // try doing this 5 times f.ex.
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
Console.WriteLine("Wait");
Console.ReadLine(); // Put a breakpoint here and inspect cwt to see that the IntPtr is still there
}