PG日誌

読者です 読者をやめる 読者になる 読者になる

PG日誌

主にc#の事を書いています

自動実装のイベントは解放されるのか?

c#

c#では、自動実装でイベントを宣言することができる。構文は以下の通りだ。

public event Action<object, EventArgs> TheEvent;

少し気になる事がある。。これは、資源が解放されるのだろうか?イベントを設定した状態でオブジェクトを捨てるとメモリリークするのではないだろうか?という疑問が浮かんだ。

というわけで実験してみた。

テストプログラム

public class Program {
    public static void Main(string[] args) {
        for (int i = 0; i < 50000; i++) {
            var case0 = new Case0(); 
        }
        for (int i = 0; i < 50000; i++) {
            var case1 = new Case1();
            case1.TheEvent += case1_TheEvent;
        }
        for (int i = 0; i < 50000; i++) {
            var case2 = new Case2();
            case2.TheEvent += case1_TheEvent;
            case2.Dispose(); // 解放処理を呼ぶ
        }
        GC.Collect(0);
        Console.WriteLine("Case0のコンストラクタの呼ばれた回数 : " + Case0.ConstructorCount + "回");
        Console.WriteLine("Case0のデストラクタの呼ばれた回数 : " + Case0.DestructorCount + "回");
        Console.WriteLine("");
        Console.WriteLine("Case1のコンストラクタの呼ばれた回数 : " + Case1.ConstructorCount + "回");
        Console.WriteLine("Case1のデストラクタの呼ばれた回数 : " + Case1.DestructorCount + "回");
        Console.WriteLine("");
        Console.WriteLine("Case2のコンストラクタの呼ばれた回数 : " + Case2.ConstructorCount + "回");
        Console.WriteLine("Case2のデストラクタの呼ばれた回数 : " + Case2.DestructorCount + "回");
        Console.ReadLine();
    }
    private static void case1_TheEvent(object arg1, EventArgs arg2) { }
}

ちなみに、今のところイベントを設定したままオブジェクトを捨てると資源の解放がされないと思っているのでCase1のデストラクタ実行回数は何回やっても0回だ。

そして、実行結果は以下の通り。

Case0のコンストラクタの呼ばれた回数 : 50000回
Case0のデストラクタの呼ばれた回数 : 49999回

Case1のコンストラクタの呼ばれた回数 : 50000回
Case1のデストラクタの呼ばれた回数 : 49999回

Case2のコンストラクタの呼ばれた回数 : 50000回
Case2のデストラクタの呼ばれた回数 : 39175

意外なことに、全てのケースで資源が解放されているようだ。
この程度のイベントの切り貼り程度なら.netが勝手に解放してくれるようだ。
なにやら実行回数がおかしいのは制御不可能なので気にしないように。

各クラスのソースコード

イベントを設定しない普通のクラス

public class Case0 {

    // コンストラクタの呼ばれた回数
    public static uint ConstructorCount;
    // デストラクタの呼ばれた回数
    public static uint DestructorCount;

    public Case0() { Case0.ConstructorCount++; }

    ~Case0() { Case0.DestructorCount++; }

    private Random r = new Random();
    public int Foo() { return r.Next(); }
}

自動実装イベントで解放処理を書かないケース

public class Case1 {

    // コンストラクタの呼ばれた回数
    public static uint ConstructorCount;
    // デストラクタの呼ばれた回数
    public static uint DestructorCount;

    // 自動実装イベント
    public event Action<object, EventArgs> TheEvent;

    public Case1() { Case1.ConstructorCount++; }

    ~Case1() { Case1.DestructorCount++ ; }

    public void Foo() { if (this.TheEvent != null) { this.TheEvent(this, new EventArgs()); } }
}

イベントプロパティ + 明示して解放処理を書くケース

public class Case2 : IDisposable {

    // コンストラクタの呼ばれた回数
    public static uint ConstructorCount;

    // デストラクタの呼ばれた回数
    public static uint DestructorCount;

    // 自動実装じゃないプロパティ
    private Action<object, EventArgs> _TheEvent;
    public event Action<object, EventArgs> TheEvent {
        add { this._TheEvent += value; }
        remove { this._TheEvent -= value; }
    }

    public Case2() { Case2.ConstructorCount++; }

    ~Case2() { Case2.DestructorCount++; }

    public void Foo() { if (this._TheEvent != null) { this._TheEvent(this, new EventArgs()); } }

    // デリゲートの削除
    public void Dispose() { this._TheEvent = null; }
}
広告を非表示にする