先日の記事で私が自作言語を作っていることは周知のことかと思いますが、そのコードを書いている際にある問題に出くわしました。Interfaceの実装問題です。C#にはTypeBuilderというクラスがあり、これを使えば、型(interfaceもclassもstructも)を定義できるはずなのですが、なぜかinterfaceを実装するclassを定義しようとしてもうまくいかない。MSDNのTypeBuilder.AddInterfaceImplemetationメソッドの例の通りに書いているのにうまくいかない、なんでだろうと1週間以上試行錯誤を経てたどり着いた結果が、生成したクラスがobjectを継承していないことでした。つまり、C#でinterfaceを継承した型を動的に生成する最小のコードは、以下のような感じになるでしょう。
using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace Test
{
class Main
{
public static void Main(string[] args)
{
var name = new AssemblyName("test");
var asm_builder = Thread.GetDomain().DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave, "./");
var file_name = "test.exe";
var mod_builder = asm_builder.DefineDynamicModule(file_name);
var interface_builder = mod_builder.DefineType("IInterface", TypeAttributes.Abstract | TypeAttributes.Interface);
interface_builder.DefineMethod("DoSomeBehavior", MethodAttributes.Public | MethodAttributes.Abstract | MethodAttributes.Virtual, typeof(int), null);
var interface_type = interface_builder.CreateType();
var type_builder = mod_builder.DefineType("TestClass", TypeAttributes.NotPublic | TypeAttributes.Class, typeof(object), new []{interface_type});
var ctor = type_builder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
CallingConventions.Standard, Enumerable.Empty<Type>().ToArray());
var il_generator = ctor.GetILGenerator();
il_generator.Emit(OpCodes.Ret);
type_builder.AddInterfaceImplementation(interface_type);
var method_builder = type_builder.DefineMethod("DoSomeBehavior", MethodAttributes.Public | MethodAttributes.Virtual, typeof(int), Enumerable.Empty<Type>().ToArray());
var il_generator2 = method_builder.GetILGenerator();
il_generator2.Emit(OpCodes.Ldc_I4_0);
il_generator2.Emit(OpCodes.Ret);
var class_type = type_builder.CreateType();
var instance = Activator.CreateInstance(class_type);
asm_builder.Save(file_name);
var method = class_type.GetMethod("DoSomeBehavior");
var return_value = method.Invoke(instance, null);
Console.WriteLine(return_value);
}
}
}
注意点としては、classにobjectを継承させる以外にも、interfaceはabstractにすること、interfaceのメソッドは、abstractかつvirtual、publicで宣言すること、classのメソッドは、publicかつvirtualで宣言することが挙げられます。今回は、フィールドを宣言したり、アクセスしたりしないので簡潔になっていますが、フィールドにメソッド内でアクセスしようとすると途端に複雑になり、TypeBuilder単独では達成できなくなったりしますが、それはまた別の話です。そこに関しては、他の方が記事にしているので、当方では記事にするつもりはありません。