7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【C#】System.Reflection.Emitで動的コード生成 初回

Last updated at Posted at 2020-05-03

注意

初めて書く記事ですのでお見苦しい点があると思います。
そのため温かい目でご覧ください。

C#でメタプログラミングをする方法

  1. Refrection.Emit(Emitter)
  2. Expression.Tree(式木)
  3. CodeDOM
  4. T4(Text Template Transformation Toolkit)

自分が認識しているのは標準ライブラリはこんな感じです
今回は 1 のRefrection.Emitを使います

Refrection.Emitについて

  • メリット
    • .netの仕組みを知れ、理解が深まる
    • C#で使えない機能を扱える
    • 他の動的コード生成の方法に比べ高速
    • .NET Framework 2.0といった古いバージョンでも利用が可能
  • デメリット
    • CIL(MSIL)の知識が必要(今後回解説する予定です)
    • 情報が少ない

このようにRefrection.Emitでは古いバージョンで動かしたい!.netの技術を深めたい!自作の言語を作りたい!コンパイラを作りたい!といった方にお勧めです。

しかしながら、簡単にコード生成をしたい!速度はどうでもいいからコードを生成を手っ取り早くやりたい!といった方にはCodeDOM、Expression.Treeをおすすめします。

##実際にコードを生成してみる##
今回は初回ですので、HelloWorldをしてみます
今回生成するファイルは以下の構文と同じ意味になります

C#
class Program
{
    static void Main()
    {
        letter = "Hello World";
        System.Console.WriteLine(letter);
    }
}

C#の構文

環境はVS2019 .NET Freamwork4.7.2で、コンソールアプリケーション(.NET Freamwork)です
.net coreについては一部動かないため方法が見つかるか、機能が追加されるまでおまちください

C#
using System;
using System.Reflection;
using System.Reflection.Emit;

class Program
{
    static void Main()
    {
        //アセンブリの設定
        string projectName = "Hello World";
        AssemblyName assemblyName = new AssemblyName() { Name = projectName };//アセンブリの名前を設定する
        AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Save);//アセンブリを生成する
        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(projectName, projectName + ".exe", true);//アセンブリのモデルを生成する
        //クラスを宣言する
        TypeBuilder typeBuilder = moduleBuilder.DefineType("Program", TypeAttributes.Class);//クラスを設定
        MethodBuilder mainMethodBuilder = typeBuilder.DefineMethod("Main", MethodAttributes.Public | MethodAttributes.Static, typeof(void), Type.EmptyTypes);//メソッドの設定
        ILGenerator ilMain = mainMethodBuilder.GetILGenerator();//ILを指定する
        LocalBuilder msgLocal = ilMain.DeclareLocal(typeof(string));//stringの変数を宣言する
        msgLocal.SetLocalSymInfo("letter");//変数名を決める
        ilMain.Emit(OpCodes.Ldstr, "Hello World");//スタックにHello Worldをプッシュする(文字列の時)
        ilMain.Emit(OpCodes.Stloc, msgLocal);//スタックの一番上から現在の値をポップし変数に格納する
        ilMain.Emit(OpCodes.Ldloc, msgLocal);//スタックに変数からプッシュする
        ilMain.EmitCall(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), null);//呼び出す
        ilMain.Emit(OpCodes.Ret);
        typeBuilder.CreateType();
        assemblyBuilder.SetEntryPoint(mainMethodBuilder);
        assemblyBuilder.Save(projectName+".exe");
    }
}

これでHello Worldを出力するexeファイルを出力します
出力先は実行ファイルを置いたパスに出力されます
8行目AssemblyBuilderを宣言するとき、引数にパスを追加することで出力先を指定できます。

AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Save,任意のパス);

当たり前のようにプッシュとかポップとかって言ってますが、.netはレジスタではなくスタックで実装されています。またILはそのまま変数から呼び出すことができず、スタックに入れてから使う必要があります。
pushではスタックに上から入れます
popではスタックの値を上から取り出します
イメージでは本を積んで下から取り出せないようになっている感じです。
また、CILの命令はOpCodesというClassに含まれているフィールドで構成されています。それに、CILではスタックにポップ、プッシュするときによって変わります。

また、実行命令については以下のサイトをご参照ください
OpCodes クラス

IL Dasmでデコンパイルしたものを見てみましょう

Developer Command Promptで

ildasm

を実行することでIL Dasmが開かれます
そこに実行ファイルをドラッグアンドドロップで置くことでCILを見ることができます

CIL構文

cil
.method public static void  Main() cil managed
{
  .entrypoint
  // コード サイズ       13 (0xd)
  .maxstack  1
  .locals init ([0] string letter)
  IL_0000:  ldstr      "Hello"
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_000c:  ret
} // end of method Program::Main

こうなってたらちゃんとできてます。

では出力ファイルを実行してみましょう

今回はConsole.ReadLine()を追加していないので、結果を見ることができないので
コマンドプロンプトで実行する必要があります。
実行したら以下のように表示されます。
これが表示されたら成功です。

Hello World

最後に

最後まで読んでいただきありがとうございます。
今回は初回ですが気が向いたら第二回以降も書こうと思います。
次回をするとしたら、staticメソッドの生成と、呼び出しについてやります。
また、番外として命令表をわかりやすくまとめたいと思います。

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?