単純派生したクラスを動的に生成する

元になるクラスから単純に派生した、つまり実装は変えずに型名だけを変えた派生クラスを必要なだけ作る必要が出てきたので、(通常ならば新しい型が必要になった時点で派生型を定義するのでリビルドも必要)ILのEmitを使ってコンストラクタだけ書き直した型を、動的に生成するメソッドを書いてみた。

public static Type CreateDynamicDelivedClass(
    string className, Type baseClass, Type[] ctorParamTypes, bool saveAssembly)
{
    AssemblyName asmName = new AssemblyName();
    asmName.Name = "DynamicDelived" + className;
    AssemblyBuilder asmBuild = Thread.GetDomain().DefineDynamicAssembly(asmName, AssemblyBuilderAccess.RunAndSave);
    ModuleBuilder modBuild = asmBuild.DefineDynamicModule("DynamicDelivedModule", asmName.Name + ".dll");

    TypeBuilder typeBuilder = modBuild.DefineType(
        className, TypeAttributes.Public | TypeAttributes.Class, baseClass);

    /* default .ctor() */
    ConstructorBuilder defconstructorBuilder =
       typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);

    /* .ctor(ctorParamTypes..) */
    ConstructorBuilder constructorBuilder =
       typeBuilder.DefineConstructor(MethodAttributes.Public,
                          CallingConventions.Standard, ctorParamTypes);
    ConstructorInfo superConstructor = baseClass.GetConstructor(ctorParamTypes);
    ILGenerator il = constructorBuilder.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    for(int i = 1; i <= ctorParamTypes.Length; i++) 
    {
        il.Emit(OpCodes.Ldarg, i); 
    }
    il.Emit(OpCodes.Call, superConstructor);
    il.Emit(OpCodes.Ret);

    Type newType = typeBuilder.CreateType();
    if (saveAssembly) asmBuild.Save(asmName.Name + ".dll"); 
    return newType;
}

ポイントはスーパクラスのコンストラクタで必要なパラメタの分だけ、パラメタをスタックにロードするところ。(0番目はthisへの暗黙の参照)

    il.Emit(OpCodes.Ldarg_0);
    for(int i = 1; i <= ctorParamTypes.Length; i++) 
    {
        il.Emit(OpCodes.Ldarg, i); 
    }

では実際に動かしてみよう。例えば、以下のような簡単なクラスがあったとして

public class HogeClass
{
    private string field1;
    private int field2;
    public HogeClass() { }
    public HogeClass(string field1, int field2)
    {
        this.field1 = field1;
        this.field2 = field2;
    }
    public void Say()
    {
        System.Console.WriteLine("field1 : " + field1 + ", field2 : " + field2);
    }
}

このクラスの派生型と、そのインスタンスを動的に作るには、以下のようなコードを書く。

Type delivedClass = CreateDynamicDelivedClass("DelivedHoge", typeof(HogeClass), new Type { typeof(string), typeof(int) }, true);
HogeClass hoge = Activator.CreateInstance(delivedClass, new object { "hello", 1 }) as HogeClass;
hoge.Say();

これで出来たアセンブリDynamicDelivedHoge.dllのコンストラクタをILDASM等で確認すると、以下のように、スーパクラスのコンストラクタを呼び出すILが出力されているはずだ。

.method public specialname rtspecialname instance void .ctor() cil managed
{
      .maxstack 2
      L_0000: ldarg.0 
      L_0001: call instance void [Hoge]HogeClass::.ctor()
      L_0006: ret 
}

.method public specialname rtspecialname instance void .ctor(string, int32) cil managed
{
      .maxstack 3
      L_0000: ldarg.0 
      L_0001: ldarg A_0
      L_0005: nop 
      L_0006: nop 
      L_0007: ldarg A_1
      L_000b: nop 
      L_000c: nop 
      L_000d: call instance void [Hoge]HogeClass::.ctor(string, int32)
      L_0012: ret 
}