委譲クラスの自動生成
http://d.hatena.ne.jp/akiramei/20040810#p3
遙か昔のネタを再利用して、委譲メソッドをチマチマ書くのを手抜きしてみる。
#region Using directives using System; using System.Collections.Generic; using System.Text; using System.Reflection; using System.Reflection.Emit; #endregion class Delegator<T> where T : class { public static T To<U>(U dst) { Type t = typeof(T); Type m = typeof(U); // 作成されるクラス名はクラスTo移譲先クラス string name = t.Name + "To" + m.Name; AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly( new AssemblyName(name + "Assembly"), AssemblyBuilderAccess.Run); ModuleBuilder md = ab.DefineDynamicModule(name + "Module"); TypeBuilder tb; if (t.IsInterface) tb = md.DefineType(name, TypeAttributes.Class, typeof(object), new Type {t}); else tb = md.DefineType(name, TypeAttributes.Class, t); tb.DefineDefaultConstructor(MethodAttributes.Public); FieldBuilder fb = tb.DefineField("_" + m.Name, m, FieldAttributes.Private); foreach (MethodInfo mi in t.GetMethods( BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Instance|BindingFlags.DeclaredOnly)) { // 仮想関数のみオーバーライドする if (mi.IsVirtual) { // パラメータの型 ParameterInfo pis = mi.GetParameters(); Type types = new Type[pis.Length]; for (int i = 0; i < pis.Length; ++i) types[i] = pis[i].ParameterType; MethodBuilder mb; if (mi.IsAbstract) mb = tb.DefineMethod(mi.Name, MethodAttributes.Public|MethodAttributes.Virtual, mi.CallingConvention, mi.ReturnParameter.ParameterType, types); else mb = tb.DefineMethod(mi.Name, mi.Attributes, mi.CallingConvention, mi.ReturnParameter.ParameterType, types); // 移譲先メソッド MethodInfo mid = m.GetMethod(mi.Name, BindingFlags.Instance|BindingFlags.Public); ILGenerator il = mb.GetILGenerator(); // パラメータをスタックに積む // this il.Emit(OpCodes.Ldarg, 0); // 移譲先クラス il.Emit(OpCodes.Ldfld, fb); // メソッド引数 for (int i = 1; i < pis.Length + 1; ++i) il.Emit(OpCodes.Ldarg, i); il.Emit(OpCodes.Call, mid); il.Emit(OpCodes.Ret); // オーバーライド tb.DefineMethodOverride(mb, mi); } } Type dt = tb.CreateType(); T obj = Activator.CreateInstance(dt) as T; dt.GetField(fb.Name, BindingFlags.Instance | BindingFlags.NonPublic).SetValue(obj, dst); return obj; } } // テスト用インタフェース public interface ICalc { int Add(int a, int b); } // テスト用抽象クラス public abstract class Calc { public abstract int Add(int a, int b); } // 移譲先 public class MyCalc { public int Add(int a, int b) { return a + b; } } class Program { static void Main(string args) { MyCalc mc = new MyCalc(); // ICalc.Addをmcへ委譲する ICalc ic = Delegator<ICalc>.To(mc); // Class.Addをmcへ委譲する Calc ac = Delegator<Calc>.To(mc); Console.WriteLine("{0}:{1}", ic.ToString(), ic.Add(10, 20)); Console.WriteLine("{0}:{1}", ac.ToString(), ac.Add(30, 40)); } } /* 結果 ICalcToMyCalc:30 CalcToMyCalc:70 */
ちょっとした実験だったのでかなり手抜きです。
- 委譲元/先メソッド名の関連を辞書で持たせる
- 移譲先が存在しない場合は空の実装を用意し、NotImplimentedExceptionを投げる
- 移譲先を複数指定可能にする
↑これくらい実装すれば使い物になるかなぁ。
久々にC#ネタをやった気が・・・(^^;