d.sunnyone.org
sunnyone.org

ページ

ラベル MSIL の投稿を表示しています。 すべての投稿を表示
ラベル MSIL の投稿を表示しています。 すべての投稿を表示

2014-05-11

MSILでわかるC# のラムダ式

そういえばLINQ to Objectsでコードを書いたときに裏でどういう動きになっているのかなぁ、とILでイメージができなかったので読んでみたら、単にC#のラムダ式がどうなっているかという話だけだったのでまとめる。

長いのでサマリ:
  • ラムダ式を使うと、基本的に入れ子クラスが作られ、ラムダ式が使う変数をとっておかれる。
  • ラムダ式の中身に書いたものは、基本的に入れ子クラスのメソッドとして定義される。
  • 入れ子クラスは不要なときには作られない。

評価スタックでピンとこない方は「C#でHelloWorldプログラムを作成する」を読んでおくのをおすすめする。

今回の説明用のサンプルコードはこちら。「.Count(x => x == val)」がどうなっていくのか、というお話。
namespace ConsoleApplicationLinq
{
    class Program
    {
        static void Main(string[] args)
        {
            int val = 100;
            var format = "Count of {0}: {1}";
            
            var array = new int[0];
            var count = array.Count(x => x == val);

            System.Console.WriteLine(format, val, count);
        }
    }
}

サイズ0のint配列に100がいくつあるか調べてWriteするというコード(0に決まっている)。説明の都合、順番などが不自然な感じになっている。

このソースをILに逆アセンブルした結果がこちら。
https://gist.github.com/sunnyone/0d3ed8285b5d91495399
別ウィンドウで開きながら説明を見るといいかも。

さて、これからILを見ていく。書いた部分がコンパイルされているであろうMainメソッドの実装を見ようとすると、そのMainメソッドの前に見知らぬ「<>c__DisplayClass1」という入れ子クラスが作られていることがわかる。

そのクラスの内容はこうなっている。

.class auto ansi sealed nested private beforefieldinit '<>c__DisplayClass1'
         extends [mscorlib]System.Object
  {
    (CompilerGeneratedAttributeの部分は省略)
    
    .field public int32 val // valフィールド
    
    (ここにコンストラクタの定義があるが省略)
    
    // <Main>b__0メソッドの定義
    .method public hidebysig instance bool 
            '<Main>b__0'(int32 x) cil managed
    {
      .maxstack  8
      
      // xをロード(評価スタックにpush)
      IL_0000:  ldarg.1
      
      // valフィールドをロード
      IL_0001:  ldarg.0
      IL_0002:  ldfld      int32 ConsoleApplicationLinq.Program/'<>c__DisplayClass1'::val
      
      // 二つの値=xとvalの値を比較し、同じなら1・異なるときは0
      IL_0007:  ceq
      
      // return
      IL_0009:  ret
    } // end of method '<>c__DisplayClass1'::'<Main>b__0'

  } // end of class '<>c__DisplayClass1'

このクラスは要は「x => x == val」の部分を<Main>b__0というメソッドに実装し、加えてvalフィールドを持っている。

次に本体のMainメソッド。Mainメソッドはこの入れ子クラスを活用して動作する。

まずメソッドとローカル変数の定義。format, array, countのほかに、先ほどの<>c__DisplayClass1が「CS$<>8__locals2」として用意されているのがわかる。valはないことに注意。
.method private hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    .maxstack  3

    .locals init ([0] string format,
             [1] int32[] 'array',
             [2] int32 count,
             [3] class ConsoleApplicationLinq.Program/'<>c__DisplayClass1' 'CS$<>8__locals2')

ここから処理開始だが、記述したC#コードに対応する部分に先立って、<>c__DisplayClass1がnewされ、ローカル変数に入る。
// <>c__DisplayClass1クラスのインスタンスを生成してローカル変数3番目にセット
    IL_0000:  newobj     instance void ConsoleApplicationLinq.Program/'<>c__DisplayClass1'::.ctor()
    IL_0005:  stloc.3

次に「int val = 100;」の部分。ここがポイント。
C#コード上ではローカル変数に見えているが、実際には内部の<>c__DisplayClass1クラスのフィールドになっている。
入れ子になったクラスにあるラムダ式の実体がこの変数を使うためにフィールドに入れている。
// CS$<>8__locals2と「100」をロードして、CS$<>8__locals2のvalにセット
    IL_0006:  ldloc.3
    IL_0007:  ldc.i4.s   100 
    IL_0009:  stfld      int32 ConsoleApplicationLinq.Program/'<>c__DisplayClass1'::val

次に「var format = "Count of {0}: {1}";」の部分。このように、ラムダ式と関係ない部分はふつうのローカル変数になる。
// 説明省略
    IL_000e:  ldstr      "Count of {0}: {1}"
    IL_0013:  stloc.0

次に「var array = new int[0];」だが、ここもローカル変数に入れるだけ。
// 説明省略
    IL_0014:  ldc.i4.0
    IL_0015:  newarr     [mscorlib]System.Int32
    IL_001a:  stloc.1

次に実際のラムダ式が登場する「var count = array.Count(x => x == val);」の部分。
先ほどvalのために生成したDisplayClassのメソッドを使ってFuncオブジェクトを生成し、Countメソッドに渡している。

// ローカル変数1番目:arrayをロード
    IL_001b:  ldloc.1
    
    // <Main>b__0メソッドのポインタをロード
    IL_001c:  ldloc.3
    IL_001d:  ldftn      instance bool ConsoleApplicationLinq.Program/'<>c__DisplayClass1'::'<Main>b__0'(int32)
    
    // <Main>b__0メソッドのポインタを使って、Funcオブジェクトを生成
    IL_0023:  newobj     instance void class [mscorlib]System.Func`2::.ctor(object,
                                                                                        native int)
                                                                                        
    // System.Linq.Enumerable::CountメソッドにarrayとFuncオブジェクトを渡す
    IL_0028:  call       int32 [System.Core]System.Linq.Enumerable::Count(class [mscorlib]System.Collections.Generic.IEnumerable`1,
                                                                                 class [mscorlib]System.Func`2)

    // ローカル変数2番目にセット
    IL_002d:  stloc.2

最後に「System.Console.WriteLine(format, val, count);」の部分。
ポイントはvalを使うのにラムダ式用の<>c__DisplayClass1を使っているところ。
// ローカル変数0番目: formatをロード
    IL_002e:  ldloc.0
    
    // ローカル変数3番目: CS$<>8__locals2のvalフィールドをロード、intなのでboxingする
    IL_002f:  ldloc.3
    IL_0030:  ldfld      int32 ConsoleApplicationLinq.Program/'<>c__DisplayClass1'::val
    IL_0035:  box        [mscorlib]System.Int32
    
    // ローカル変数2番目: countをロード, boxing
    IL_003a:  ldloc.2
    IL_003b:  box        [mscorlib]System.Int32
    
    // WriteLine
    IL_0040:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                  object,
                                                                  object)
    IL_0045:  ret
  } // end of method Program::Main

このように、ラムダ式は、必要な変数を入れ子クラスのインスタンスにとっておいて、ラムダ式に書いた内容のメソッドが実行される、という形で実装されている。
しかし、ラムダ式があれば必ず入れ子クラスが作られるかというとそうではなく、変数をとっておく必要がない場合、違う形にコンパイルされる。

たとえば、先のコードのint val = 100;にconstをつけてconst int val = 100;にするだけで、ILはこうなってしまう。

.class private auto ansi beforefieldinit ConsoleApplicationLinq.Program
       extends [mscorlib]System.Object
{
  // ProgramクラスそのものにFuncのフィールドが用意される
  .field private static class [mscorlib]System.Func`2<int32,bool> 'CS$<>9__CachedAnonymousMethodDelegate1'
  
  (CompilerGeneratedAttributeの部分は省略)
  
  .method private hidebysig static void  Main(string[] args) cil managed
  {
    // ローカル変数の定義などなど
    .entrypoint
    .maxstack  3
    .locals init ([0] string format,
             [1] int32[] 'array',
             [2] int32 count)
    IL_0000:  ldstr      "Count of {0}: {1}"
    IL_0005:  stloc.0
    IL_0006:  ldc.i4.0
    IL_0007:  newarr     [mscorlib]System.Int32
    IL_000c:  stloc.1
    IL_000d:  ldloc.1
    
    // CS$<>9__CachedAnonymousMethodDelegate1をロードして、存在すればこの先の処理まで飛ばす
    IL_000e:  ldsfld     class [mscorlib]System.Func`2 ConsoleApplicationLinq.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
    IL_0013:  brtrue.s   IL_0026

    // なければProgramクラスに定義された<Main>b__0メソッド(「x => x == val」の実装)を使ってFuncオブジェクトを作る
    IL_0015:  ldnull
    IL_0016:  ldftn      bool ConsoleApplicationLinq.Program::'<Main>b__0'(int32)
    IL_001c:  newobj     instance void class [mscorlib]System.Func`2::.ctor(object,
                                                                                        native int)
    // 作ったらフィールドに格納して、ロードしておく
    IL_0021:  stsfld     class [mscorlib]System.Func`2 ConsoleApplicationLinq.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
    IL_0026:  ldsfld     class [mscorlib]System.Func`2 ConsoleApplicationLinq.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
    
    // 以下同じ
    IL_002b:  call       int32 [System.Core]System.Linq.Enumerable::Count(class [mscorlib]System.Collections.Generic.IEnumerable`1,
                                                                                 class [mscorlib]System.Func`2)
    IL_0030:  stloc.2
    IL_0031:  ldloc.0
    IL_0032:  ldc.i4.s   100
    IL_0034:  box        [mscorlib]System.Int32
    IL_0039:  ldloc.2
    IL_003a:  box        [mscorlib]System.Int32
    IL_003f:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                  object,
                                                                  object)
    IL_0044:  ret
  } // end of method Program::Main

  (コンストラクタの定義は省略)
  
  .method private hidebysig static bool  '<Main>b__0'(int32 x) cil managed
  {
    (CompilerGeneratedAttributeの部分は省略)
    
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldc.i4.s   100 // 「100」はここに埋め込まれるので、フィールドから取る必要がない
    IL_0003:  ceq
    IL_0005:  ret
  } // end of method Program::'<Main>b__0'

} // end of class ConsoleApplicationLinq.Program

こっちのコードだとご丁寧にもFuncオブジェクトをstaticフィールドにキャッシュしている。

ちなみに、valをローカル変数ではなくフィールドに持つようなクラスを作った場合も上述のconstの形に近くなり、入れ子クラスは作られない(Funcオブジェクトのキャッシュはしなくなる)。

まぁ、この差異はプログラム全体からしたらたいしたことはないと思うので、書くときに意識することはないと思うが、知っておいても悪くない…かな?
(一応、array.Count(x => x == val)を10000000回実行したら、valがconst/フィールド/ローカル変数それぞれの場合でStopwatchクラス読みで240ms/240ms/300msだった)

こうしてILコードを見ていると、ラムダ式がどういうものなのかしっくりきたのだけど、みなさまはどうだろうか?

2012-10-25

C#の"ref"引数のMSIL的取り扱い(.NETと参照渡し)

C#でrefを使ったメソッドのMSIL的作り方/呼び出し方(参照渡しの方法)についてメモしておく。

サマリ

  • メソッドに渡す際は、アドレスを渡す。Cのポインタで言う&valのイメージ。
    • 引数のpushには ldloca命令を使う。
  • メソッド内では、アドレスの解決を行う。Cのポインタで言う*aのイメージ。
    • 取得にはldind命令シリーズ(ldobj)、設定にはstind命令シリーズ(stobj)を使う。
  • 型は、実際の型に&がついた「ref型」を利用する(例: System.Int32&)
    • Type.MakeByRefTypeでref型化、Type.GetElementTypeで非ref型化できる。
    • ref型かどうかは、Type.IsByRefで判断できる。

例

具体例として、参照を使うコードにありがちなSwapメソッドを作ってみる。

C#コード

void Swap(ref int x, ref int y) {
    int tmp;
    tmp = x;
    x = y;
    y = tmp;
}

void SampleFunc()
{
    int x = 10; int y = 20;
    Swap(ref x, ref y);
    System.Console.WriteLine("x, y: {0},{1}", x, y);
}

これをコンパイルしたものを逆アセンブルしたものが以下。
.method private hidebysig instance void  Swap(int32& x,
                                              int32& y) cil managed
{
  .maxstack  2
  .locals init ([0] int32 tmp)
  IL_0000:  nop
  IL_0001:  ldarg.1
  IL_0002:  ldind.i4
  IL_0003:  stloc.0
  IL_0004:  ldarg.1
  IL_0005:  ldarg.2
  IL_0006:  ldind.i4
  IL_0007:  stind.i4
  IL_0008:  ldarg.2
  IL_0009:  ldloc.0
  IL_000a:  stind.i4
  IL_000b:  ret
}

.method private hidebysig instance void  SampleFunc() cil managed
{
  .maxstack  3
  .locals init ([0] int32 x,
           [1] int32 y)
  IL_0000:  nop
  IL_0001:  ldc.i4.s   10
  IL_0003:  stloc.0
  IL_0004:  ldc.i4.s   20
  IL_0006:  stloc.1
  IL_0007:  ldarg.0
  IL_0008:  ldloca.s   x
  IL_000a:  ldloca.s   y
  IL_000c:  call       instance void ConsoleApplication1.Program::Swap(int32&,
                                                                       int32&)
  IL_0011:  nop
  IL_0012:  ldstr      "x, y: {0},{1}"
  IL_0017:  ldloc.0
  IL_0018:  box        [mscorlib]System.Int32
  IL_001d:  ldloc.1
  IL_001e:  box        [mscorlib]System.Int32
  IL_0023:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object,
                                                                object)
  IL_0028:  nop
  IL_0029:  ret
} // end of method Program::SampleFunc

呼ばれるSwapメソッド

適当に端折りながら、上から見ていく。
(評価スタックのイメージを忘れてしまった場合は、この絵を思い出してほしい。)

まずメソッドの定義。
.method private hidebysig instance void  Swap(int32& x,
                                              int32& y) cil managed
int32ではなく、int32&型が使われている。これは、
Type t = typeof(Int32).MakeByRefType();
てな感じで得られる。

そしてtmp = x;の部分。
IL_0001:  ldarg.1
  IL_0002:  ldind.i4
  IL_0003:  stloc.0
これは、
  1. 1番目の引数(ref x)を持ってくる
  2. 持ってきたアドレスからint32の値を持ってくる
  3. 0番目のローカル変数に格納する
というイメージである。

x = y;はいったん飛ばして、y = tmp;部分を見るとこう。
IL_0008:  ldarg.2
  IL_0009:  ldloc.0
  IL_000a:  stind.i4
これは、
  1. 2番目の引数(ref y)を持ってくる
  2. 0番目のローカル変数からint32の値を持ってくる
  3. 持ってきたアドレスに持ってきた値を入れる
というイメージである。

x = yは上記の組み合わせ。
IL_0004:  ldarg.1
  IL_0005:  ldarg.2
  IL_0006:  ldind.i4
  IL_0007:  stind.i4

ここでは、int32なので、ldind.i4/stind.i4だが、型によって使うべき命令は違う。ただし、
すべての ldind 命令は、対応している組み込み値クラスを指定する Ldobj 命令のショートカットです。
(OpCodes.Ldind_I4 フィールド)なので、ldobj [type]でもよい。

ldobj/stobjの例としては、ref int?を使うと、以下の感じで生成される。
IL_0008: stobj valuetype [mscorlib]System.Nullable`1

Swap()を呼ぶメソッド

こちらは実際に呼び出しているところだけ。
IL_0001:  ldc.i4.s   10
  IL_0003:  stloc.0
  IL_0004:  ldc.i4.s   20
  IL_0006:  stloc.1
  IL_0007:  ldarg.0
  IL_0008:  ldloca.s   x
  IL_000a:  ldloca.s   y
  IL_000c:  call       instance void ConsoleApplication1.Program::Swap(int32&,
                                                                       int32&)
10、20をそれぞれx、yに入れたあと、this、xのアドレス、yのアドレスの順に持ってきて、関数をコールしている。

リフレクションでの扱い

すでにあるメソッドのMethodInfoをGetMethod()で持ってきて、ParameterTypeを見ると、
&のついたref型で入っている。この元の型を得たいときは、こんな感じになる。
if (parameterType.IsByRef) {
  Type valueType = parameterType.GetElementType();
}
(Type.MakeByRefType メソッド)

2012-08-19

C#でHelloWorldプログラムを作成する

C#を使い始めたので、ひとつ入門記事でも。Hello, Worldするプログラムを作るよ。

まずはソースから。


いつもの数行のコードだと思った?Hello, Worldプログラム自体を生成するプログラムでした。

プログラムの使い方はこう。第一引数にexeのファイル名を指定すると、Hello, World!を出力するexeファイルを作成する。
c:\temp> HelloWorld1.exe Hello.exe
c:\temp> Hello.exe
Hello, World!

スタックのイメージ

説明の前に、この絵がとても大事なので覚えてほしい。このイメージがあれば世のMSIL説明記事と戦える。

.NETの世界では、ローカル変数、スタック、引数という3つの枠があり、それぞれ必要に応じて移動して使う。引数の枠から、値をスタックに移動してきて、なんらかの操作を実行、必要に応じてローカル変数に退避する、という感じかな。

ソースの説明

さて、実際にHello, World生成クラスを作ってみる。static void Main()でWriteLine()するだけで本当は十分なのだが、説明のためにインスタンスを生成してSay()というメソッドを呼び出すようになっている。

なお、ソースの頭のところは、お決まりの引数処理だったり.NETの仕組みみたいな話なので省略して進める。

1. クラスの作成

TypeBuilder helloClassBuilder = moduleBuilder.DefineType("Hello", TypeAttributes.Class);
ConstructorBuilder ctorBuilder = helloClassBuilder.DefineDefaultConstructor(MethodAttributes.Public);
ModuleBuilderのDefineType()というメソッドを使い、クラスを作る。
あとでnewしたいので、デフォルトコンストラクタのConstructorBuilderをとっておく。

2. インスタンスメソッドSay()の作成

文字列をコンソールに出力するSay()を作成する。生成後をC#で書くとこんな感じ。
public void Say(string value)
{
 System.Console.WriteLine(value);
}

生成するコードは以下。
MethodBuilder sayMethodBuilder = helloClassBuilder.DefineMethod("Say", 
    MethodAttributes.Public, 
    typeof(void), 
    new Type[] { typeof(string) });
TypeBuilderのDefineMethod()を使って、メソッドを作る。"Say"という名前で、public、戻り値はvoid、ひとつの引数stringを取るという感じ。

ILGenerator ilSay = sayMethodBuilder.GetILGenerator();
MethodBuilderのGetILGenerator()というメソッドを呼び出すと、ILを生成していくためのILGeneratorオブジェクトを得られる。アセンブラコードを書いていくようなイメージ。このあと、Emit()やEmitなんとか()を利用して書き込んでいく。

// System.Console.WriteLine()にSayの第一引数を渡してコール
ilSay.Emit(OpCodes.Ldarg_1);
ilSay.EmitCall(OpCodes.Call,
    typeof(System.Console).GetMethod("WriteLine", new Type[] { typeof(String) }),
    null);

// Sayを抜ける
ilSay.Emit(OpCodes.Ret);

まずldarg_1を使って、関数の引数の1番目を評価スタックにロードする。なお、0はインスタンス。C#はthisはメソッド定義に書いてないけど、最初の引数がselfだったりthisだったりする言語あるよね。そんな感じ。

そして、callを使って、System.Console.WriteLineを呼び出す。
OpCodes.Callには、MethodInfoを渡してあげる必要があるが、これはリフレクションで取ってきてしまう。
上記の例では、stringという引数を持つWriteLineというメソッドを、System.Consoleのタイプから拾ってくる形である。

なぜCallのときはEmitではなく、EmitCallを使っているかというと、可変長引数を持つメソッド用の型をEmit()が受け取ることができないから。ここでは使ってないので、実はEmitでよい。

最後にreturnたるretを書いて、Sayの実装は終わり。

3. Mainの作成

エントリポイントとなるMain()を実装する。生成後のものをC#で書くとこんな感じ。
private static void Main(string[] array)
{
 string text = "Hello, World";
 Hello hello = new Hello();
 hello.Say(text);
}

生成するコードは以下。
MethodInfo thisMainMethod = System.Reflection.Assembly.GetExecutingAssembly().EntryPoint;
MethodBuilder mainMethodBuilder = helloClassBuilder.DefineMethod(
    thisMainMethod.Name,
    thisMainMethod.Attributes,
    thisMainMethod.ReturnType,
    (from p in thisMainMethod.GetParameters() select p.ParameterType).ToArray<Type>());
ILGenerator ilMain = mainMethodBuilder.GetILGenerator();
Mainメソッドは、この生成プログラム自体の定義からもらってきた。実際にILGeneratorを使ってILを作っていくような場面では、生成側のオブジェクトの定義をリフレクションでもらってくるケースが多いと思う。

3.1. ローカル変数の作成

// ローカル変数を宣言してメッセージを格納
LocalBuilder msgLocal = ilMain.DeclareLocal(typeof(string));
ilMain.Emit(OpCodes.Ldstr, "Hello, World");
ilMain.Emit(OpCodes.Stloc, msgLocal);
試しにローカル変数を作ってみた例。DeclareLocal()を呼ぶと、ローカル変数が宣言できるので、評価スタックに値をロードしてから、stlocを使って、そこに保存する。

3.2. インスタンスの生成

// ローカル変数を宣言してHelloオブジェクトを生成・格納
LocalBuilder helloLocal = ilMain.DeclareLocal(helloClassBuilder);
ilMain.Emit(OpCodes.Newobj, ctorBuilder);
ilMain.Emit(OpCodes.Stloc, helloLocal);
インスタンスを作成するときは、newobjにコンストラクタを渡す。ここに限った話じゃないけど、Emit達は、~Builderを受けてくれる。すごくよくできていると思う。

3.3. Sayメソッドを実行

// 作成したオブジェクトに対して、Sayメソッドを実行
ilMain.Emit(OpCodes.Ldloc, helloLocal);
ilMain.Emit(OpCodes.Ldloc, msgLocal);
ilMain.EmitCall(OpCodes.Call, sayMethodBuilder, null);
インスタンス、引数の順にldlocを使ってスタックに詰めて、callでSayメソッドを呼び出す。

なお、お気づきかもしれないが、「3.2. インスタンスの生成」で、オブジェクトはスタックに生成されているので、いちいちローカル変数に入れなくてもいい。文字列も固定なので同様。なので、3.1.~は、以下のように書くこともできる。
ilMain.Emit(OpCodes.Newobj, ctorBuilder);
ilMain.Emit(OpCodes.Ldstr, "Hello, World");
ilMain.EmitCall(OpCodes.Call, sayMethodBuilder, null);

ilMain.Emit(OpCodes.Ret);
retを呼んで終了。


あとは、エントリポイントを指定して、アセンブリをファイルにSaveするだけ。

ここまで読んだあなたは、C#を使って、プログラムを生成することができるようになっているはず。
フィールドとか、Genericとか、よく使いそうなものを全然解説していないけど、そこは「詳しくはWebで」という感じで。

Javaでも同じようなことがきっとできると思う。Cはどうだろう?LLVMとかの力を借りればいいのかな。

IL生成の罠

間違えると実行時のエラーがわかりにくいので、大変である。
以下は例。

ret忘れちゃった

// Sayを抜ける
// ilSay.Emit(OpCodes.Ret);

ハンドルされていない例外: System.InvalidProgramException: 共通言語ランタイムが無効なプログラムを検出しました。
場所 Hello.Say(String )
場所 Hello.Main(String[] )

スタックから取るの忘れた


// ローカル変数を宣言してメッセージを格納
LocalBuilder msgLocal = ilMain.DeclareLocal(typeof(string));
ilMain.Emit(OpCodes.Ldstr, "Hello, World");
// ilMain.Emit(OpCodes.Stloc, msgLocal);

ハンドルされていない例外: System.InvalidProgramException: JIT コンパイラで内部的な制限が発生しました。
場所 Hello.Main(String[] )

気をつけるべし。

参考

動的処理 « C#たんっ! http://csharptan.wordpress.com/2011/12/16/%E5%8B%95%E7%9A%84%E5%87%A6%E7%90%86/
KEN's .NET IL入門 http://www5b.biglobe.ne.jp/~yone-ken/VBNET/IL/il01_FirstStep.html
ラッパークラスの生成 - 匣の向こう側 - あまりに.NETな http://d.hatena.ne.jp/akiramei/20040810/1345306640

余談

実際のところ、「dynamic」もあるし、System.Reflection.EmitでILを書くことになるケースはほとんどないと思う。ダイナミックに関数を生成したいだけなら、Expression Treeという便利なものもあるし。ただ、type-safeなものをなんとかして作りたいなんてレアなときに有用かもしれない。