ILGenerator を使用して記述された配列アクセスを含むシンプルな for ループがあります。この正確なコードでメソッドが作成されたら、逆アセンブリを開きますが、配列の境界チェックはありません。
しかし、最初に他のクラスのインスタンスを評価スタックに置いてから for ループを実行すると、配列の境界チェックが行われます。リリースに向けて動いています。
理由はありますか?配列境界チェックに関するブログ投稿を既に読みました: http://blogs.msdn.com/b/clrcodegeneration/archive/2009/08/13/array-bounds-check-elimination-in-the-clr.aspx
// Uncomment this to enable bound checks, type of arg0 is some my class
//il.Emit(OpCodes.Ldarg_0);
var startLbl = il.DefineLabel();
var testLbl = il.DefineLabel();
var index = il.DeclareLocal(typeof(Int32));
var arr = il.DeclareLocal(typeof(Int32).MakeArrayType());
// arr = new int[4];
il.Emit(OpCodes.Ldc_I4_4);
il.Emit(OpCodes.Newarr, typeof(Int32));
il.Emit(OpCodes.Stloc, arr);
// Index = 0
il.Emit(OpCodes.Ldc_I4_0); // Push index
il.Emit(OpCodes.Stloc, index); // Pop index, store
il.Emit(OpCodes.Br_S, testLbl); // Go to test
// Begin for
il.MarkLabel(startLbl);
// Load array, index
il.Emit(OpCodes.Ldloc, arr);
il.Emit(OpCodes.Ldloc, index);
// Now on stack: array, index
// Load element
il.Emit(OpCodes.Ldelem_I4);
// Nothing here now, later some function call
il.Emit(OpCodes.Pop);
// Index++
il.Emit(OpCodes.Ldloc, index);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Stloc, index);
il.MarkLabel(testLbl);
// Load index, count, test for end
il.Emit(OpCodes.Ldloc, index);
il.Emit(OpCodes.Ldloc, arr);
il.Emit(OpCodes.Ldlen); // Push len
il.Emit(OpCodes.Conv_I4); // Push len
il.Emit(OpCodes.Blt_S, startLbl);
// End for
// Remove instance added on top
//il.Emit(OpCodes.Pop);
IL コードを生成するときに、クラスのインスタンスを評価スタックまたはローカル変数に保持する方がよいでしょうか?
たとえば、インスタンスを取得し、フィールドを通過し、各フィールドに対して何かを実行してから戻ります。インスタンスをスタックに保持し、次のフィールドを読み取る前に Emit(OpCodes.Dup) を呼び出しました。しかし、それは間違っているようです(少なくとも上記の場合)。
(効率的で整形式の) IL コードの生成に関する記事やブログ投稿を歓迎します。