かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

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作ったりとか色々やってるんだなというのがわかった今日この頃でした。頭が疲れました。