ネタ元 http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=28778&forum=7&5
こんな話は大好きだ。
確かに、ローカルスコープで作ったインスタンスは、ローカル変数しか参照していなく、他に誰も参照していなかったら、いつか GC の対象になる。Form とて例外ではなかろう。しかし、実際はそうではない。Form は明確に誰も参照していなくても、GC の対象とならないのだ。(後で述べるが、実際は参照している奴が存在する)
Form に限らずコントロールは全てウィンドウハンドルを持つ。コントロールは全て「ウィンドウ」なのだ。.NET ではウィンドウハンドルを持つウィンドウを「System.Windows.Forms.Control」として扱っている。各コントロールはこのクラスから派生しているわけだ。Form もその一つである。Control は内部に System.Windows.Forms.NativeWindow のインスタンスを持ち、ウィンドウハンドルを管理している。Control は Dispose() 時に NativeWindow.DestroyHandle() を呼んでウィンドウハンドルを破棄しているはずだ。
NativeWindow のヘルプを見ると「ウィンドウは、ウィンドウ ハンドルが関連付けられている場合は、ガベージ コレクションの対象にはなりません。」とある。何故だ?どう実現している?
詳細に調べていくと、NativeWindow は内部に GCHandle のインスタンスを保持していた。NativeWindow がウィンドウハンドルに関連付けられていると GC の対象にならないのは GCHandle が鍵なのだ。
GCHandle.Alloc() で新しい GCHandle を作成する。そして GCHandle.Target にガベージコレクションの対象としたくないインスタンスの参照を設定してやればよいのだ。今回の例で言えば、トップレベルの Form のインスタンスの参照だ。(ここでトップレベルの Form のインスタンスの参照をどのように得ているのかは調査中)
結局、Form は自身が内部に持つ GCHandle に参照される事になる。後は GCHandle が誰かに参照されれば問題は解決だ。
GCHandle は誰に参照されているのか?
これは CLR しか預かり知らぬ所。CLR が管理している領域でもあるのか、GC が管理しているのかは分からない。(情報求ム)とにかく、GCHandle は GCHandle.Alloc() したら誰かに参照されている状態で取得できる。
改めて .NET の設計の巧さに拍手だ。
ウィンドウハンドルに限らず、GCHandle を使えば GC を上手に使いこなす事ができるだろう。