有的時候,我們需要給某些資料新增一些附加資訊,一種常用的做法是使用一個Dictionary在填充這些附加資訊如:
var data = new Data();
var tag = new
Tag();
var dictionary = new
Dictionary<Data, Tag>();
dictionary[data] = tag;
這麼做本身沒有什麼問題,但是卻又一個不小的隱患,那就是在dictionary中儲存著了data和tag的引用。當data不再使用的時候,需要將其從dictionary中移除,否則data和tag得不到釋放。我們可以用如下程式碼說明這個問題:(注意,由於Debug模式有時會影響GC,本文程式碼需行在Release模式下)
class
Tag
{
public Tag()
{
Console.WriteLine("Create Tag");
}
~Tag()
{
Console.WriteLine("Release Tag");
}
}
class
Data
{
public Data()
{
Console.WriteLine("Create Data");
}
~Data()
{
Console.WriteLine("Release Data");
}
}
static
void Main(string[] args)
{
var data = new
Data();
var tag = new
Tag();
var dictionary = new
Dictionary<Data, Tag>();
dictionary[data] = tag;
data = null;
GC.Collect();
Console.WriteLine("After GC");
Console.ReadLine();
Console.WriteLine(dictionary);
}
從執行結果中可以看出,只有建立的輸出,而沒有釋放的輸出。這個就屬於資源洩漏了。雖然可以通過手動在dictionary中刪除data來實現資源的釋放,但是這樣就要求我們手動管理物件的生命週期了,而這往往不是一個比較容易做到的事情。
究其原因,是由於dictionary中保持著強引用、導致GC不會對其進行回收。找到了這個原因後,那就有相應的對策了,那就是改用弱引用來建立關聯,這樣資料就會被GC釋放了。這種觀念關係我們通常稱為弱字典——WeakDictionary。弱字典也是儲存著Key和Value的鍵值對,它滿足如下需求:
-
字典中儲存著Key的弱引用,即使不釋放Key值,也可以被GC回收。
-
字典中儲存的Value的強引用,Key沒有被GC回收前,Value不會被GC回收。
-
當Key被GC回收時,關聯關係從字典中移除,Value也能被GC回收。
知道了需求後,接下來就可以對Dictionary進行簡單的封裝,將其改造成弱字典了。
static
void Main(string[] args)
{
var data = new
Data();
var tag = new
Tag();
var dictionary = new
Dictionary<WeakReference<Data>, Tag>();
var key = new
WeakReference<Data>(data);
dictionary[key] = tag;
data = null;
GC.Collect();
Console.WriteLine("After GC");
Console.ReadLine();
Console.WriteLine(dictionary);
}
執行這段程式碼後,我們就會發現,Data資料能釋放了,但是並不完善,具體體現在如下方面:
-
Tag儲存的仍然是強引用,得不到釋放
-
Key資料並不是Data型別了,存在一個檢索的問題,否則無法CRUD。
對於第一個問題,可以通過一個Timer來定時清理已經釋放了的Key來解決;對於第二個問題,則需要在內部通過key來建立Hash表來解決。具體的實現還有點麻煩,也會引入一些新的問題,這裡就不繼續列舉了。
之所以不繼續改造下去了,是因為這裡我是在造重複輪子,.Net的BCL中本身就已經提供了一個弱字典——ConditionalWeakTable,通過ConditionalWeakTable改造上述程式碼如下:
static
void Main(string[] args)
{
var data = new
Data();
var tag = new
Tag();
var dictionary = new
ConditionalWeakTable<Data, Tag>();
dictionary.Add(data, tag);
data = null;
GC.Collect();
Console.WriteLine("After GC");
Console.ReadLine();
Console.WriteLine(dictionary);
}
從執行結果來看,GC結束後,Key和Value都被GC回收掉了(再次強調,需要執行在Release版本下)。
這個類放置在System.Runtime.CompilerServices下,也很少見到有書裡面介紹到它。這裡我就簡單的介紹一下其介面吧:
dictionary.Add(data, tag); //新增
dictionary.TryGetValue(data, out tag); //查詢
dictionary.Remove(data); //刪除
這三個是它比較常見的介面,另外還有兩個不大用的介面,這裡就不多介紹了。
最後,簡單的試了它的效能,基本上和Dictionary差不多,查詢效率還是非常高的,內部應該也是一個Hash表,。