Ktouth Brand. on Web

け〜くんこと K.Ktouth のだらだらした日常と突発的に作るプログラムや読み物とかの雑多サイト



[2012年01月28日]

ラムダ式の外のローカル変数の保持の仕方は……?

2012年01月28日 22:16更新 筆者:K.Ktouth

ちょっと気になったことがあったので簡単に調べた結果を自分メモ。
ラムダ式、正確にはデリゲートを "弱い参照" でラップしたクラスを作ってみようかなと思ったんですが、ラムダ式って、式内部で「式の外で定義されたローカル変数」を参照出来るんですよね。
でもリファレンスマニュアルでのDelegateのメンバ一覧にあるプロパティは「Target」と「Method」だけ。どこにローカル変数をスタックしているのか……?

ということでちょっとばかり実験。
Delegateを「Target」と「Method」に分解してリフレクションを使ってメソッドを実行するという荒技をしてみたところ、普通にローカル変数を維持したままで実行出来てしまいましたびっくり
で、実際にはどうなってると、デバッガ上でインスペクタ表示をたぐってみると……ローカル変数をメンバ変数として持つ無名クラスが生成され、それを経由する?形で無名メソッドを呼び出すという扱いになっている模様。コンパイラがどういうコードを吐いているのかまでは追っていませんが、きちんと最低でも必要なローカル変数はフォロー出来る模様。案外シンプルなコードで分解可能ですね。

とりあえず、検証に使ったコードを掲載。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      var proc = SampleA(15);
      proc();

      SampleA(1935)();

      var procB = SampleB(proc);

      GC.Collect();
      procB();

      proc = null;
      GC.Collect();
      procB();

      Console.ReadLine();
    }

    static Action SampleA(int num)
    {
      return () => { Console.WriteLine("{0} をお知らせします♪", num); };
    }

    static Action SampleB(Action proc)
    {
      var obj = new WeakReference(proc.Target);
      MethodInfo info = proc.Method;
      return SampleB_(obj, info);
    }
    static Action SampleB_(WeakReference target, MethodInfo method)
    {
      return () =>
      {
        object obj;
        try { obj = target.Target; }
        catch (InvalidOperationException) { obj = null; }
        if (obj != null) { method.Invoke(obj, null); }
        else { Console.WriteLine("アクションはGCずみです。"); }
      };
    }
  }
}

本日のリンク元
その他のリンク元
検索