在 dotnet 中有一個特殊的類,這個類能夠做到附加屬性一樣的功能。也就是給某個對象附加一個屬性,當這個對象被回收的時候,自然解除附加的屬性的對象的引用。本文就來聊聊這個類的底層原理 ...
在 dotnet 中有一個特殊的類,這個類能夠做到附加屬性一樣的功能。也就是給某個對象附加一個屬性,當這個對象被回收的時候,自然解除附加的屬性的對象的引用。本文就來聊聊這個類的底層原理
小伙伴都知道弱緩存是什麼,弱緩存的核心是弱引用。也就是我雖然拿到一個對象,但是我沒有給這個對象添加依賴引用,也就是這個對象不會記錄被弱引用的引用。而 ConditionalWeakTable 也是一個弱緩存只是有些特殊的是關聯的是其他對象。使用方法請看 .NET/C# 使用 ConditionalWeakTable 附加欄位(CLR 版本的附加屬性,也可用用來當作弱引用字典 WeakDictionary) - walterlv
這個類一般用來做弱緩存字典,只要 Key 沒有被回收,而 value 就不會被回收。如果 key 被回收,那麼 value 將會減去一個依賴引用。而字典對於 key 是弱引用
通過閱讀 runtime 的源代碼,可以看到實際上這個類的核心需要 DependentHandle 結構體的支持,因為依靠 key 定住 value 需要 CLR 的 GC 支持。什麼是依靠 key 定住 value 的功能?這裡的定住是 Pin 的翻譯,意思是如果 key 存在記憶體,那麼將會給 value 添加一個引用,此時的 value 將不會被回收。而如果 key 被回收了,此時的 value 將失去 key 對他的強引用
換句話說,只要 key 的值存在,那麼 value 一定不會回收
這個功能純使用 WeakReference 是做不到的,需要 GC 的支持,而在 dotnet core 裡面提供 GC 支持的對接的是 DependentHandle 結構體
那麼 DependentHandle 的功能又是什麼?這個結構體提供傳入 object primary, object? secondary
構造函數,作用就是當 primary 沒有被回收的時候,給 secondary
添加一個引用計數。在 primary 回收的時候,解除對 secondary 的引用。而這個結構體本身對於 primary 是弱引用的,對於 secondary 僅在 primary 沒有被回收時是強引用,當 primary 被回收之後將是弱引用
剛好利用 GC 的只要對象至少有一個引用就不會被回收的功能,就能做到 ConditionalWeakTable 提供附加屬性的功能
下麵代碼是 DependentHandle 結構體的代碼,可以看到大量的方法都是需要 GC 層的支持,屬於 CLR 部分的註入方法
internal struct DependentHandle
{
private IntPtr _handle;
public DependentHandle(object primary, object? secondary) =>
// no need to check for null result: nInitialize expected to throw OOM.
_handle = nInitialize(primary, secondary);
public bool IsAllocated => _handle != IntPtr.Zero;
// Getting the secondary object is more expensive than getting the first so
// we provide a separate primary-only accessor for those times we only want the
// primary.
public object? GetPrimary() => nGetPrimary(_handle);
public object? GetPrimaryAndSecondary(out object? secondary) =>
nGetPrimaryAndSecondary(_handle, out secondary);
public void SetPrimary(object? primary) =>
nSetPrimary(_handle, primary);
public void SetSecondary(object? secondary) =>
nSetSecondary(_handle, secondary);
// Forces dependentHandle back to non-allocated state (if not already there)
// and frees the handle if needed.
public void Free()
{
if (_handle != IntPtr.Zero)
{
IntPtr handle = _handle;
_handle = IntPtr.Zero;
nFree(handle);
}
}
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern IntPtr nInitialize(object primary, object? secondary);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern object? nGetPrimary(IntPtr dependentHandle);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern object? nGetPrimaryAndSecondary(IntPtr dependentHandle, out object? secondary);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void nSetPrimary(IntPtr dependentHandle, object? primary);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void nSetSecondary(IntPtr dependentHandle, object? secondary);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void nFree(IntPtr dependentHandle);
}
而核心實現的入口是在 gchandletable.cpp 的 OBJECTHANDLE GCHandleStore::CreateDependentHandle(Object* primary, Object* secondary)
代碼,這部分屬於更底的一層了,在功能上就是實現上面的需求,而實現上為了性能優化,代碼可讀性還是渣了一些
要實現這個功能需要在 GC 層裡面寫上一大堆的代碼,但使用上現在僅有 ConditionalWeakTable 一個在使用