[C#] 關於 Delegate 的 GC 測試 – Testing if delegate will prevent an object to be GC

針對一個小疑惑進行小測試:

如果有 delegate 指向物件 A 底下的方法 (class’s member function) 時,物件 A 是否會被系統 GC (garbage collection) 呢?

雖然預期測試結果是不會被 GC 啦!姑且還是測試看看,並找找相關資料。

delegate-test

測試程式碼

using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;

public class DelegateTest : MonoBehaviour {

    Action callback = null;
    ClassA testObject = null; 

    void Start() {
        AddCallback ();
        CountClassA ();
        RunCallback ();

        DestroyClassA ();
        RunCallback ();
        CountClassA ();

        CleanCallback ();
        RunCallback ();
        CountClassA ();
    }

    public void AddCallback() {
        if (testObject == null) {
            testObject = new ClassA ();
        }

        callback += testObject.FunctionA;
    }

    public void RunCallback () {
        if (callback != null) {
            callback ();
        } else {
            Debug.Log ("null callback");
        }
    }

    public void CleanCallback () {
        callback = null;
        GC.Collect ();
    }

    public void DestroyClassA() {
        testObject = null;
        GC.Collect ();
    }

    public void CountClassA () {
        Debug.Log ("ClassA count: " + ClassA.Count);
    }

}

public class ClassA {
    public static int Count = 0;

    public ClassA () {
        Count += 1;
    }

    ~ClassA () {
        Count -= 1;
    }

    public void FunctionA () {
        Debug.Log ("FunctionA called");
    }
}

測試結果

delegate-test

一如預期,如果還有 delegate 使用到物件底下的方法,物件則不會被 GC 銷毀。

其實 delegate 這個類別底下有個 Target 的成員會指向來源方法的所屬物件,因此原物件嚴格來說並不符合沒被參照 (no incoming reference) 的 GC 條件,不被系統 GC 回收是理所當然的。

不過因此延伸出來的議題便是,如果使用不當,可能會因為 delegate 導致有很多物件明明失去作用了卻還是無法被 GC,而導致程式的記憶體洩漏 (memory leak),在使用 delegate 必須更加小心得去管理。

如果你使用的是匿名委託 (anonymous delegate),則對應的 delegate 會更容易被忽略掉。比如這樣的寫法:

someCallback += delegate { DoSomething(foo); };

是無法利用簡單的運算來移除委託的,最後只能銷毀整個 delegate,或者在一開始有另外準備變數管理。

// 無效的做法
someCallback -= delegate { DoSomething(foo); };

// 較適當的作法
Action callbackRef = delegate { DoSomething(foo); };
someCallback += callbackRef;
someCallback -= callbackRef;

// 最終手段
someCallback.Clear();

延伸閱讀

在找資料的過程中,看到了一篇不完全相關的有趣實驗,在實驗中可以發現如果使用 event 這個關鍵字在 delegate 變數上,則在 Add / Reomve 的過程中,會有額外記憶體成本,並且是隨著 delegate 數量不斷累進的。

Do Events and Delegates Create Garbage? – http://jacksondunstan.com/articles/3264

廣告

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s