RunningCSharp

MS系開発者による、雑多な記事。記事は所属企業とは関係のない、個人の見解です。

C#:キャプチャした変数が解放されないパターンについて

    class Program
    {
        static void Main(string[] args)
        {
            localMethod();

            MyAction();

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.ReadLine();
        }

        private static void localMethod()
        {
            MyClass myClass = new MyClass();

            MyAction += () =>
            {
                //ラムダ式内でローカル変数myClassを参照
                myClass.Method();
            };
        }

        public class MyClass
        {
            public void Method()
            {
                Console.WriteLine("Closure");
            }

            ~MyClass()
            {
                Console.WriteLine("Destructor");
            }
        }

        private static Action MyAction;
    }

結果:

Closure

上記処理では、localMethodメソッド内でnewしたMyClassがMyActionに参照されたため、 オブジェクトの寿命がMyActionに引きずられることでデストラクタが呼び出されません。

対策として、Main関数で下記のようにMyActionにnullを代入すればデストラクタが読みだされます。

        static void Main(string[] args)
        {
            localMethod();

            MyAction();

            //null代入を追加
            MyAction = null;

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.ReadLine();
        }

結果:

Closure

Destructor

なお、ラムダ式を直接-=演算子で代入してもメソッド登録を解除できないため、MyActionの宣言が

private static event Action MyAction;

であった場合は、別のActionオブジェクトに=でラムダ式を格納し、そのラムダ式でMyActionに+=で登録、-=で登録解除するといった工夫が必要そうです。