delegateに代入された特定条件を満たしたラムダに関する一考察。
ある方に相談したら、何となくわかってきたので、後でまた忘れたときのために。
で、満たすべき条件とは
- 閉包*1を使ってない
と言う点、この条件が意味するところは、ラムダ式をStatic methodとしてコンパイル可能であるという点。逆に、閉包を使ってないのなら、ブロックにしてしまって、複数行にわたって書いても構わないみたい。*2。
この条件を満たしたサンプルは以下の通り
using System; namespace ConsoleApplication1 { internal class Program { private static void Main(string[] args) { Func<int, int, int> add = (x, y) => x + y; Action<int> twiceAndPrint = _ => Console.WriteLine(_*2); var result = add(10, 20); twiceAndPrint(result); } } }
さて、こいつをコンパイルしたとき、cscはどのようなILを吐くのか調べてみたところ、C#で、再構築してみると概ね以下のような形になった。
using System; namespace ConsoleApplication1 { internal class Program { private static Func<int, int, int> CachedAnonymousMehod0; private static Action<int> CachedAnonymousMehod1; private static void Main(string[] args) { //隠しキャッシュがnullだったら、登録する。 if (CachedAnonymousMehod0 == null) { CachedAnonymousMehod0 = new Func<int, int, int>(AddImpl); } if (CachedAnonymousMehod1 == null) { CachedAnonymousMehod1 = new Action<int>(TwiceAndPrintImpl); } //Func<int, int, int> add = (x, y) => x + y; Func<int, int, int> add = CachedAnonymousMehod0; //Action<int> twiceAndPrint = _ => Console.WriteLine(_*2); Action<int> twiceAndPrint = CachedAnonymousMehod1; //var result = add(10, 20); int result = add(10, 20); //twiceAndPrint(result); twiceAndPrint(result); } //(x, y) => x + y private static int AddImpl(int x, int y) { return x + y; } //_=>Console.WriteLine(_*2) private static void TwiceAndPrintImpl(int _) { Console.WriteLine(_*2); } } }
実際には、ラムダ式の部分は、そのクラスのprivate staticなメソッドに展開され、デリゲートのキャッシュを持つことになる。
じっさいここまでは、以前から調べは付いていたけど、何故またキャッシュするのか?と言う点に関して、尤もらしい理由を付けることが出来なかった*3。
で、先に戻って、ある方に相談してその会話の中でふと思いついたのが、
- 実装の単純性
って点だった。これは何かというと、恐らく、コンパイラがキャッシュを生成してデリゲートの構築コストを省こうとするコンパイル結果を生成する理由として、参照先のラムダ式は概ね単純だろうと言う仮定に基づいているのでは無いかなと。
思い返すと、Func
この場合、もしかしたら
- デリゲート経由で実装を呼び出して結果を返す時間的コスト<デリゲートを1回1回キャッシュ無しで生成する時間的コスト
となり得ることが多いと仮定して、もしかしたら繰り返しエンクロージャ側のメソッドが呼ばれるコトを想定して、キャッシュしてるのでは無いかなと*5
逆に、一般的なメソッドを参照させるときは、別のメソッドとして存在してるくらいだから、それなりに中でやってることも複雑だろう、時間がかかるだろうと言うことと、恐らく、静的メソッドと、インスタンスメソッドを分けて考えるようなことはしないために、キャッシングしないのじゃ無いかと思う。
と言うことで、備忘録的なメモ。