.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回 ループでやってみたところ、メモリが増えている様子はない。
ということは消えているんじゃないかと予測される。