.Net中的弱引用字典WeakDictionary和ConditionalWeakTable介紹

有的時候,我們需要給某些數據添加一些附加信息,一種常用的做法是使用一個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表。

到此這篇關於.Net弱引用字典WeakDictionary和ConditionalWeakTable的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: