クラスの動的生成

久しぶりに.NETのプログラムを話題を。
.NET2.0でDataGridViewというデータをGridを表示するコントロールがありまして、そこにデータのEntityを自動的にBindするために BindingSourceというコントロールが用意されています。これを使うとデータクラスのプロパティをGridの列にBindしてくれる優れものなのです。ついでに、この自動Bindingにはプロパティ名を使ったリフレクションが使われています。

そんな前置きがありつつ、今回はこの列を動的に変えたいと思ったわけです。

さて、ここからが本題です。それなら動的にプロパティを増やせるクラスを用意できれば、あとは BindingSourceが面倒見てくれるのでは?と考えて実行してみました。結論から言えば、大成功でした。その方法ですが、.NET Frameworkのリフレクションを使うとアセンブリを動的に生成できるので、それを使ってクラスを生成してしまうというものです。あとは生成されたクラスのインスタンスを作れば、作ったクラスのプロパティと列のBindingは勝手に BindingSourceが行ってくれるということです。

しかし、クラスを動的に生成するにはメソッドの実装自体はアセンブリを手書きする必要があったのが誤算でした。仕方ないので、ilasmを使って見本クラスのアセンブリを確認しながら、実装していきました。結論としては以下のような感じのコードで無事実装完了しました。

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace TestIL {
    public interface IUserData {
        string[] GetProperties();
    }

    public class CreateType {
        private static Type Create(string className, string[] propertyNames) {
            AppDomain domain = AppDomain.CurrentDomain;

            AssemblyName assemblyName = new AssemblyName();
            assemblyName.Name = "TempAssembly.dll";
            AssemblyBuilder assemblyBuilder = domain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);

            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("SubModule", assemblyName.Name);
            TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class, typeof(object), new Type[] { typeof(IUserData) });

            // GetProperties
            MethodBuilder getPropsMethod = typeBuilder.DefineMethod("GetProperties", MethodAttributes.Public | MethodAttributes.Virtual, typeof(string[]), Type.EmptyTypes);
            ILGenerator getPropsIL = getPropsMethod.GetILGenerator();
            getPropsIL.DeclareLocal(typeof(string[]));
            LoadInteger(getPropsIL, propertyNames.Length);
            getPropsIL.Emit(OpCodes.Newarr, typeof(string));
            getPropsIL.Emit(OpCodes.Stloc_0);
            for (int index = 0; index < propertyNames.Length; index++) {
                getPropsIL.Emit(OpCodes.Ldloc_0);
                LoadInteger(getPropsIL, index);
                getPropsIL.Emit(OpCodes.Ldstr, propertyNames[index]);
                getPropsIL.Emit(OpCodes.Stelem_Ref);
            }
            getPropsIL.Emit(OpCodes.Ldloc_0);
            getPropsIL.Emit(OpCodes.Ret);


            MethodAttributes propAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

            foreach (string name in propertyNames) {
                FieldBuilder nameFieldBuilder = typeBuilder.DefineField(name + "_", typeof(string), FieldAttributes.Private);
                PropertyBuilder namePropertyBuilder = typeBuilder.DefineProperty(name, PropertyAttributes.HasDefault, typeof(string), null);

                MethodBuilder getNameMethod = typeBuilder.DefineMethod("get_" + name, propAttr, typeof(string), Type.EmptyTypes);
                ILGenerator getNamePropIL = getNameMethod.GetILGenerator();
                getNamePropIL.Emit(OpCodes.Ldarg_0);
                getNamePropIL.Emit(OpCodes.Ldfld, nameFieldBuilder);
                getNamePropIL.Emit(OpCodes.Ret);

                MethodBuilder setNameMethod = typeBuilder.DefineMethod("set_" + name, propAttr, null, new Type[] { typeof(string) });
                ILGenerator setNamePropIL = setNameMethod.GetILGenerator();
                setNamePropIL.Emit(OpCodes.Ldarg_0);
                setNamePropIL.Emit(OpCodes.Ldarg_1);
                setNamePropIL.Emit(OpCodes.Stfld, nameFieldBuilder);
                setNamePropIL.Emit(OpCodes.Ret);

                namePropertyBuilder.SetGetMethod(getNameMethod);
                namePropertyBuilder.SetSetMethod(setNameMethod);
            }

            Type retval = typeBuilder.CreateType();

            assemblyBuilder.Save(assemblyName.Name);
            return retval;
        }
        private static void LoadInteger(ILGenerator il, int i) {
            switch (i) {
                case 0: il.Emit(OpCodes.Ldc_I4_0); break;
                case 1: il.Emit(OpCodes.Ldc_I4_1); break;
                case 2: il.Emit(OpCodes.Ldc_I4_2); break;
                case 3: il.Emit(OpCodes.Ldc_I4_3); break;
                case 4: il.Emit(OpCodes.Ldc_I4_4); break;
                case 5: il.Emit(OpCodes.Ldc_I4_5); break;
                case 6: il.Emit(OpCodes.Ldc_I4_6); break;
                case 7: il.Emit(OpCodes.Ldc_I4_7); break;
                case 8: il.Emit(OpCodes.Ldc_I4_8); break;
                case -1: il.Emit(OpCodes.Ldc_I4_M1); break;
                default:
                    if (-128 <= i && i <= 127) {
                        il.Emit(OpCodes.Ldc_I4_S, i);
                    } else {
                        il.Emit(OpCodes.Ldc_I4, i);
                    }
                    break;
            }
        }

        public static string GetProperty(object obj, string propertyName) {
            PropertyInfo info = obj.GetType().GetProperty(propertyName, typeof(string));
            if (info == null || !info.CanRead) {
                return null;
            }
            return info.GetValue(obj, null) as string;
        }
        public static void SetProperty(object obj, string propertyName, string value) {
            PropertyInfo info = obj.GetType().GetProperty(propertyName, typeof(string));
            if (info == null || !info.CanWrite) {
                return;
            }
            info.SetValue(obj, value, null);
        }


        private const string DEFALT_CLASS_NAME = "UserData";
        private readonly Type type_;

        public CreateType(string[] propertyNames)
            : this(DEFALT_CLASS_NAME, propertyNames) {
        }
        public CreateType(string className, string[] propertyNames) {
            type_ = Create(className, propertyNames);
        }

        public IUserData NewInstance() {
            return Activator.CreateInstance(type_) as IUserData;
        }
    }
}