Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9513

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

$
0
0

注意

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

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#
classProgram{staticvoidMain(){letter="Hello World";System.Console.WriteLine(letter);}}

C#の構文

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

C#
usingSystem;usingSystem.Reflection;usingSystem.Reflection.Emit;classProgram{staticvoidMain(){//アセンブリの設定stringprojectName="Hello World";AssemblyNameassemblyName=newAssemblyName(){Name=projectName};//アセンブリの名前を設定するAssemblyBuilderassemblyBuilder=AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName,AssemblyBuilderAccess.Save);//アセンブリを生成するModuleBuildermoduleBuilder=assemblyBuilder.DefineDynamicModule(projectName,projectName+".exe",true);//アセンブリのモデルを生成する//クラスを宣言するTypeBuildertypeBuilder=moduleBuilder.DefineType("Program",TypeAttributes.Class);//クラスを設定MethodBuildermainMethodBuilder=typeBuilder.DefineMethod("Main",MethodAttributes.Public|MethodAttributes.Static,typeof(void),Type.EmptyTypes);//メソッドの設定ILGeneratorilMain=mainMethodBuilder.GetILGenerator();//ILを指定するLocalBuildermsgLocal=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,Type.GetType("System.Console").GetMethod("WriteLine",newType[]{typeof(string)}),null);//呼び出すilMain.Emit(OpCodes.Ret);typeBuilder.CreateType();assemblyBuilder.SetEntryPoint(mainMethodBuilder);assemblyBuilder.Save(projectName+".exe");}}

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

AssemblyBuilderassemblyBuilder=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メソッドの生成と、呼び出しについてやります。
また、番外として命令表をわかりやすくまとめたいと思います。


Viewing all articles
Browse latest Browse all 9513

Trending Articles