委譲クラスの自動生成

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#ネタをやった気が・・・(^^;