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

this の寿命

.NET

先ほどのHow to use SafeHandle in a Resilient LibraryGC.KeepAliveの話が出ていたので久しぶりに実験と.
関連話題.

以下のようなコードを実行してみます.

using System;
using System.Text;
using System.Threading;
using System.Diagnostics;

public class Test
{
    IntPtr MyHandle = IntPtr.Zero;
    ~Test()
    {
        Trace.WriteLine("Finalize; Release Handle");
    }
    public void Hoge()
    {
        Trace.WriteLine("Get Handle from OS : 123456");
        this.MyHandle = (IntPtr) 123456;
        Fuga(MyHandle);
    }
    public void Fuga(IntPtr handle)
    {
        GC.Collect();
        Thread.Sleep(1000);
        Trace.WriteLine("Use Handle : " + handle);
    }
}
class Program
{
    static void Main(string[] args)
    {
        Trace.Listeners.Add(new ConsoleTraceListener());
        new Test().Hoge();
    }
}

Visual C# 2005 にて Release ビルドしたものを手元の環境で実行してみたところこのように表示されました.

Get Handle from OS : 123456
Finalize; Release Handle
Use Handle : 123456

結果を一言で言えば,「CLR は時としてインスタンスメソッド実行中に delete this を行います」というところですかね.
もうちょっと弄ると,「コンストラクタ実行中にファイナライザが走る」のを見ることができます.

public class Test2
{
    IntPtr MyHandle = IntPtr.Zero;
    ~Test2()
    {
        Trace.WriteLine("Finalize; Release Handle");
    }
    public Test2()
    {
        Trace.WriteLine("Get Handle from OS : 123456");
        this.MyHandle = (IntPtr)123456;
        IntPtr handle = MyHandle;
        GC.Collect();
        Thread.Sleep(1000);
        Trace.WriteLine("Use Handle : " + handle);
    }
}
class Program
{
    static void Main(string[] args)
    {
        Trace.Listeners.Add(new ConsoleTraceListener());
        new Test2();
    }
}
Get Handle from OS : 123456
Finalize; Release Handle
Use Handle : 123456

原因については上の関連話題で紹介されていたLifetime, GC.KeepAlive, handle recycling で言及されていますが,JIT のインライン展開によってメソッド呼出し元の this 参照が消失することがあるためです.ソースコードの上ではインスタンスメソッドの呼出し元が存在しても,そのことによってオブジェクトの存在期間がメソッド全体(つまり,呼出し元に戻る)まで保証されるというわけではない,と.少し調べ直してみたら,この問題「Rico Mariani's Performance Tidbits」の IDisposable に関する議論でも登場してますね.