かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

Expression Treeをこねてみた

id:neueccさんに影響されて(ミーハー)Expression Treeをこねてみました。
やりたいことは以下のコードです。

var vm = new ViewModel();
// ここの下一行をExpression Treeで組みたい
vm.HogeCommand = new DelegateCommand<int>(vm.HogeExecute, vm.CanHogeExecute);

id:neueccさんのやり方を参考にこねこねした結果、以下のようなコードになりました。

namespace ConsoleApplication2
{
    using System;
    using System.Linq.Expressions;
    using System.Reflection;

    // ダミーコマンド
    public class DelegateCommand<T>
    {
        public DelegateCommand(Action<T> a, Func<T, bool> b) { }
    }

    // ダミーViewModel
    public class ViewModel
    {
        public DelegateCommand<int> HogeCommand { get; set; }

        public void HogeExecute(int i) { Console.WriteLine("OK"); }
        public bool CanHogeExecute(int i) { return true; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // ViewModel型のHogeCommandを初期化するアクションを作成する
            var initHogeCommandAction = CreateInitCommandAction<ViewModel>("HogeCommand");

            // ViewModelを作ってコマンドの初期化をしてもらう
            var model = new ViewModel();
            initHogeCommandAction(model);

            // 初期化出来てるか表示してみる(nullじゃなくなってるはず)
            Console.WriteLine(model.HogeCommand);
        }

        static Action<TViewModel> CreateInitCommandAction<TViewModel>(string commandName)
        {
            // CreateDelegateのMethodInfoを作る
            var createDelegate = typeof(Delegate).GetMethod(
                "CreateDelegate",
                new[] { typeof(Type), typeof(object), typeof(MethodInfo) });

            // ExecuteのMethodInfoの変数
            var execMethod = typeof(TViewModel).GetMethod(commandName.Replace("Command", "Execute"));
            var execVar = Expression.Constant(execMethod);
            
            // CanExecuteのMethodInfoの変数
            var canExecMethod = typeof(TViewModel).GetMethod("Can" + commandName.Replace("Command", "Execute"));
            var canExecVar = Expression.Constant(canExecMethod);

            // コマンドのパラメータタイプ
            var argumentType = execMethod.GetParameters()[0].ParameterType;

            // パラメータ型を指定したActionとFuncの型を取得
            var actionType = Expression.GetActionType(argumentType);
            var canExecuteActionType = Expression.GetFuncType(argumentType, typeof(bool));

            // DelegateCommandのコンストラクタを取得
            var delConstructor = typeof(DelegateCommand<>)
                .MakeGenericType(argumentType).GetConstructor(
                    new[] { actionType, canExecuteActionType });

            // パラメータはTViewModel型
            var param = Expression.Parameter(typeof(TViewModel));

            // Executeメソッドのデリゲートを作成する式
            var createExecDelegate = Expression.Call(
                createDelegate,
                Expression.Constant(actionType),
                param,
                execVar);

            // CanExecuteメソッドのデリゲートを作成する式
            var createCanExecDelegate = Expression.Call(
                createDelegate,
                Expression.Constant(canExecuteActionType),
                param,
                canExecVar);

            var lambda = Expression.Lambda<Action<TViewModel>>(
                Expression.Assign(
                    // コマンドのプロパティに
                    Expression.PropertyOrField(param, commandName),
                    // DelegateCommandのインスタンスを設定
                    Expression.New(
                        delConstructor,
                        Expression.Convert(createExecDelegate, actionType),
                        Expression.Convert(createCanExecDelegate, canExecuteActionType))),
                param);

            // コンパイル!!
            return lambda.Compile();
        }
        

    }
}

たったの一行のコードなのにExpression Treeにすると凄い長いです!!!まぁ、あの一行が暗黙的にDelegate作ったりとか色々やってるんだなというのがわかった今日この頃でした。頭が疲れました。