技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

.NET を理解する9

ガベコレされないパターンもあると覚えておく。

.NET でガベージコレクションがあるので、みんな結構油断してると思うけど、意外なところでメモリリークすることがある。
代表例を以下に示してみる。

class SampleEventer
{
    int vals;

    public SampleEventer(int val)
    {
        vals = val;
    }

    public void ExecuteEvent(object target, EventArgs args)
    {
        Console.WriteLine("Call event as :" + vals.ToString());
    }
}

class Program
{
    public event EventHandler SampleEvent;

    public void InvokeEvent()
    {
        this.SampleEvent(this, null);
    }

    static void Main(string[] args)
    {
        Program eventSender = new Program();

        for (int i = 0; i < 100; i++)
        {
            SampleEventer target = new SampleEventer(i);
            eventSender.SampleEvent += new EventHandler(target.ExecuteEvent);
        }

        // この時点でガベコレ実行。
        System.GC.Collect();

        // 直接参照の残ってないオブジェクトの
        // イベントハンドラが実行される。
        eventSender.InvokeEvent();
    }
}

やってみればわかるが 0 - 99 の表示がされてしまう。
まぁウィンドウ1個のプログラムだとか、短時間しか起動しないなら特に問題にならないことが多い。
ただ、イベントを動的に設定する場合など、参照をきちんと設定しておかないと上記みたいな結果になる。

なので、きちんとメモリから消える方法をとってみる。

class SampleEventer
{
    int vals;

    public SampleEventer(int val)
    {
        vals = val;
    }

    public void ExecuteEvent(object target, EventArgs args)
    {
        Console.WriteLine("Call event as :" + vals.ToString());
    }
}

class Program
{
    public event EventHandler SampleEvent;

    public void InvokeEvent()
    {
        if (this.SampleEvent != null)
        {
            this.SampleEvent(this, null);
        }
    }

    static void Main(string[] args)
    {
        Program eventSender = new Program();
        List<SampleEventer> eventTargetList = new List<SampleEventer>();

        for (int i = 0; i < 100; i++)
        {
            SampleEventer target = new SampleEventer(i);
            eventSender.SampleEvent += new EventHandler(target.ExecuteEvent);

            // イベントを登録させたオブジェクトを保持しておく。
            eventTargetList.Add(target);
        }
        
        /*
         このへんで何らかの処理
         */

        // イベントを明示的に削除する
        foreach (SampleEventer target in eventTargetList)
        {
            eventSender.SampleEvent -= new EventHandler(target.ExecuteEvent);
        }

        // この時点でガベコレ実行。
        System.GC.Collect();

        // イベント除去されてきちんと消える。
        eventSender.InvokeEvent();
    }
}

「+=」したものを「-=」で外しただけ。
キャッシュするのが面倒といえば面倒ですよね、、、、どっかでライブラリ化してしまうか?
ちなみに、メモリリークした後で eventSender の参照を消した場合どうなるか。

上記を 80000回 ループでやってみたところ、メモリが増えている様子はない。
ということは消えているんじゃないかと予測される。