はかせのラボ

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

IL コンストラクタの次はデストラクタを作ろうとしてハマった話

あいさつ

どうも、はかせです。
前回コンストラクタでハマった話を上げました。

コンストラクタやったならデストラクタもやってみたい。

ということでやりました。

デストラクタとは

インスタンスGCで回収された際に呼ばれるもので
内部的にはFInalizeというメソッドです。

どういう扱いなんだろう

前回取り扱ったコンストラクタは専用の取得メソッドがあったり、
それ用のクラスがあったり大分特別扱いされていました。

じゃデストラクタはどうなってるんでしょうか?
f:id:hakase0274:20190923173628p:plain
f:id:hakase0274:20190923173535p:plain

検索結果0・・・・
デストラクタはどうやら普通のメソッドと同じ扱いのようです。

作ってみる

とりあえずこんな感じのものを目指します。

class TestIL
{
    public TestIL()
    {
        Console.WriteLine("コンストラクタよばれたよ");
    }

    ~TestIL()
    {
        Console.WriteLine("デストラクタよばれたよ");
    }
}

コンストラクタは前回やったのと同じ感じなので省略します。
詳しくはこちらをどうぞ
hakase0274.hatenablog.com

じゃデストラクタ作ってきましょう。
扱いが普通のメソッドと同じなら
普段通りやればいいかしら・・・?

var finalize = typeBuilder.DefineMethod("Finalize", MethodAttributes.Family | MethodAttributes.HideBySig | MethodAttributes.Virtual, null, null);
var il = finalize.GetILGenerator();

var label = il.DefineLabel();

il.Emit(OpCodes.Ldstr, "デストラクタよばれたよ");
il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }));
il.Emit(OpCodes.Leave_S, label);

il.Ldarg(0);
il.Emit(OpCodes.Call, typeof(object).GetMethod("Finalize"));
il.Emit(OpCodes.Endfinally);

il.MarkLabel(label);
il.Emit(OpCodes.Ret);

それ実行
f:id:hakase0274:20190923211322p:plain

ん?objectのデストラクタが取れてない。
名前はFinalizeであってるしなんでだろ・・・?

(ググり中・・・・・)

なにやらGetMethodはBIndingFlagsなるものを指定しないとPublicメンバーのみ探すらしい。
NonPublicってのがあったからそれつけてやってみよう。
f:id:hakase0274:20190923174435p:plain

えぇ・・・・
NonPublicでもダメって詰んでるじゃないですかー・・
さてはILでデストラクタって扱えない感じですかね?

救世主が現れた

Twitterで以下のサイトを紹介してもらいました。
teratail.com
プログラムで無理やりFinalizeを呼び出してみたらどうなるって感じの内容です。

さてこのサイトで使われているBindingFlagsを指定してやってみます。

il.Emit(OpCodes.Call, typeof(object).GetMethod("Finalize",BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance));

f:id:hakase0274:20190923174837p:plain

行けた!
ILはどんなの吐かれてるかなwkwk

// TestIL
using System;

public class TestIL
{
	public TestIL()
	{
		//Error decoding local variables: Signature type sequence must have at least one element.
		Console.WriteLine("コンストラクタよばれたよ");
	}

	~TestIL()
	{
		//Discarded unreachable code: IL_000c
		//Error decoding local variables: Signature type sequence must have at least one element.
		Console.WriteLine("デストラクタよばれたよ");
	}
}

うん、概ね想定通り。
良き良き(^ω^)

あとがき

今回はILでデストラクタを定義してみた話でした。

何やらデストラクタってあのBindingFlagsで
完全一致じゃないと取れないらしいです。
(めんどくさい)

そんな特別な立ち位置ならそれ用のメソッドでも用意してくれればいいのに・・・
こんな感じで

public static MethodInfo GetFinalize<T>()
{
    return typeof(T).GetMethod("Finalize", BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance);
}

(ないから今回作ったやつです)

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