はかせのラボ

私の頭の中を書いていく雑記ブログです

IL コンストラクタ作ろうとしてハマってた話

あいさつ

どうも、はかせです。
前回メソッドプロキシでの自動メモ化を頑張りました。
hakase0274.hatenablog.com

今回の記事は前回の続きではなく、
前々回の補足という形になります。

というのも本当はこの記事を書いた後で
前回の記事を書こうと思ってたのですが、
思った以上に前回のメソッドプロキシに手間取り、
手間取った分早く書きたいと思ってしまい
こんなことになってしまいました。

そんなこんなで今回の記事は
ILを使ったコンストラクタの作り方と
私がハマった話です。

ILでコンストラクタを作る方法

作り方自体はメソッドと大差ありません。
DefineMethodからDefineConstructorに変わるだけです。

//コンストラクタ作る
//第一引数 メソッド情報
//第二引数 メソッドの呼ばれ方
//第三引数 コンストラクタの引数
var ctor = typeBuilder.DefineConstructor
    (MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
    CallingConventions.Standard,
    null);
//IL打ち込む
var ctorIl = ctor.GetILGenerator();
//ローカル変数宣言
ctorIl.DeclareLocal(dictionaryType);
//継承元であるTのコンストラクターを呼ぶ
ctorIl.Emit(OpCodes.Ldarg_0);
ctorIl.Emit(OpCodes.Call, typeof(T).GetConstructor(Type.EmptyTypes));
//Dictionaryの生成を行う
ctorIl.Emit(OpCodes.Ldarg_0);
ctorIl.Emit(OpCodes.Newobj, dictionaryConstructor);
ctorIl.Emit(OpCodes.Stfld, dictionaryField);
ctorIl.Emit(OpCodes.Ret);

このコードが吐くILはこんな感じになります

public HakaseMemoization0c346398-6e17-464c-b818-7cbf4778e42e()
{
	Field_7c31a8a2df5b495587614624fe0d9a28 = new Dictionary<Memoization.Param, HakaseMemo>();
}

なんてことはない
ただ自分がもつDictionaryをnewするだけです。

ハマった点

私がコンストラクタを作るときにハマったのは以下の二点です。
・コンストラクタ引数
・コンストラクタの最初で呼び出す親のコンストラク

コンストラクタ引数

要はこの部分

//コンストラクタ作る
//第一引数 メソッド情報
//第二引数 メソッドの呼ばれ方
//第三引数 コンストラクタの引数
var ctor = typeBuilder.DefineConstructor
    (MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
    CallingConventions.Standard,
    null);

このDefineCOnstructorの第三引数を最初こうしてました。

new[] { typeof(ConstructorInfo) }

ConstructorInfoってのは名前の通りコンストラクタの情報です。
なんでこうしてたかっていうと
「IL コンストラクタ 作る」でググって
一番最初に出てきたサンプルコードにそう書いてたからです。

そしてこの場合吐かれるコードはこちら

public HakaseMemoization39fdcbad-7f1e-4909-922e-fda357c7e4c1(ConstructorInfo P_0)
{
	Field_f0155179195849d999fbecc5075b6db8 = new Dictionary<Memoization.Param, HakaseMemo>();
}

引数ができましたね。
ただこの引数つかいません。
なんなら私は引数付きでコンストラクタを呼んでません。

ただコンストラクタ的にはこの引数をよこせって
定義してるわけです。

何が起こるかもうわかりましたかね。
そうこいつがやってくるわけですよ。
System.MissingMethodException

いやぁ・・なんでこいつが出てくるのか
小一時間悩んでましたからねw

ただわかったらそらそうだなって感じでしたけど。

コンストラクタの最初で呼び出す親のコンストラク

要はこの部分

//継承元であるTのコンストラクターを呼ぶ
ctorIl.Emit(OpCodes.Ldarg_0);
ctorIl.Emit(OpCodes.Call, typeof(T).GetConstructor(Type.EmptyTypes));

ハマってた時はこんな感じ

//継承元であるobjectのコンストラクターを呼ぶ
ctorIl.Emit(OpCodes.Ldarg_0);
ctorIl.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));

何が間違ってるかわかりましたかね?
わかったらあなたは私より数倍天才です。

コンストラクタの実行順番ってのは親→子です。
(デストラクタは子→親です)

普段C#で書いてるときはそんなん意識しなくてもいいんですが、
IL書いてるとそうはいきません。
ちゃんと親のコンストラクタを最初に呼ぶ必要があります。

さて私が書いてるコードは前々回の記事を読んでもらえれば
わかりますが、継承のオーバーライドの仕組みを使った自動メモ化です。
hakase0274.hatenablog.com

つまりTを継承しメモ化対象のメソッドをオーバーライドしているわけですね。
そうTを継承しているわけです。
Tが親なんです。

ここでハマってた時のコードをもう一度

//継承元であるobjectのコンストラクターを呼ぶ
ctorIl.Emit(OpCodes.Ldarg_0);
ctorIl.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));

Tのコンストラクタ呼んでないやんけ
確かに何も継承しないクラスを作ったならこれでいいんですけどね。

ただ今回はTを継承しているわけです。
Tのコンストラクタを呼んであげる必要があります。

そうしてようやく最初のコード

//継承元であるTのコンストラクターを呼ぶ
ctorIl.Emit(OpCodes.Ldarg_0);
ctorIl.Emit(OpCodes.Call, typeof(T).GetConstructor(Type.EmptyTypes));

ここに行きつくわけですね。

コンストラクタの実行順序把握してないとか
ドシロートかよって話ですねw

あとがき

今回は一日飛んじゃいましたけど
コンストラクタ作ろうとしてハマってた部分の話でした。

いかに脳死でプログラム作ってたかがよくわかるミスばっかりでしたね。
こういう基礎の復習にもIL書きはぴったりですね。

この記事を読んでるC#erで
IL書いたことないって人は是非書いてみましょう。

大丈夫です。
私が今まで変な地雷は踏んできて
全部このブログの記事に上げてますから
きっと変なミスで困ることはないはずです。(多分)

それでは今回はこの辺でノシ