LoginSignup
2
2

More than 3 years have passed since last update.

ReactivePropertyの初期化が面倒なのでまとめて初期化してみる(Plain Object版)

Posted at

M <-> VM をBindするのにプロパティ一個一個 ToReactiveProperty() していくのが面倒くさい

特に INotifyPropertyChanged を実装できない PlainObject の場合。

    public class ReactiveBinder<TSource, TTarget>
    {
        private readonly static Dictionary<string, Action<TSource, TTarget>> binderActions = new Dictionary<string, Action<TSource, TTarget>>();

        static ReactiveBinder()
        {
            BuildBinders();
        }

        public void Bind(TSource source, TTarget target)
        {
            foreach(var action in binderActions)
            {
                action.Value.Invoke(source, target);
            }
        }

        private static void BuildBinders()
        {
            var targetType = typeof(TTarget);
            var sourceType = typeof(TSource);
            MethodInfo fromObject = GetBinderMethod(targetType);

            var sourceParameter = Expression.Parameter(sourceType, "$source");
            var targetParameter = Expression.Parameter(targetType, "$destination");
            var modeParameter = Expression.Constant(ReactivePropertyMode.Default);
            var ignoreValidationErrorValue = Expression.Constant(false);

            foreach (var property in targetType.GetProperties())
            {
                var sourceProperty = sourceType.GetProperty(property.Name);
                var sourcePropertyAccessor = Expression.Property(
                    Expression.Convert(sourceParameter, sourceType),
                    property.Name);

                var propertySelector = Expression.Lambda(sourcePropertyAccessor, sourceParameter);

                var genericMethod = fromObject.MakeGenericMethod(new Type[] { sourceType, sourceProperty.PropertyType });
                var fromObjectCall = Expression.Call(genericMethod, sourceParameter, propertySelector, modeParameter, ignoreValidationErrorValue);

                var bindingProperty = Expression.Property(
                    targetParameter,
                    property);

                var assignment = Expression.Assign(bindingProperty, fromObjectCall);

                var lambda = Expression.Lambda<Action<TSource, TTarget>>(
                    assignment,
                    sourceParameter,
                    targetParameter
                    );

                binderActions.Add(property.Name, lambda.Compile());
            }
        }
        private static MethodInfo GetBinderMethod(Type targetType)
        {
            MethodInfo fromObject = null;
            Type[] paramTypes = new Type[] {
                targetType,
                typeof(int),
                typeof(ReactivePropertyMode),
                typeof(bool)
            };
            var flags = BindingFlags.Static | BindingFlags.Public;
            foreach (MethodInfo method in typeof(ReactiveProperty).GetMethods(flags))
            {

                if (method.IsGenericMethod && method.IsGenericMethodDefinition && method.ContainsGenericParameters)
                {
                    if (method.Name == "FromObject" && method.GetParameters().Length == paramTypes.Length)
                    {
                        fromObject = method;
                        break;
                    }
                }
            }
            return fromObject;
        }
    }

Refletion と Expressionを駆使してViewModel のプロパティに ReactiveProperty.FromObject() の呼び出しを代入していく。

Example

Model

    public class SampleModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime? UpdateDateTime { get; set; }
        public bool Flag { get; set; }
    }

ViewModel

    public class SampleItemViewModel : BindableBase, INotifyPropertyChanged
    {

        public ReactiveProperty<int> Id { get; set; }
        public ReactiveProperty<string> Name { get; set; }
        public ReactiveProperty<DateTime?> UpdateDateTime { get; set; }
        public ReactiveProperty<bool> Flag { get; set; }

        public SampleItemViewModel()
        {

        }

        public SampleItemViewModel(SampleModel model)
        {
            var binder = new ReactiveBinder<SampleModel, SampleItemViewModel>();
            binder.Bind(model, this);
        }
    }

注: FromObjectメソッドを取得するのにパラメータの数の一致だけ見てるけど、オーバーロードはパラメータの数ではなくてパラメータの型をチェックするはずなので破綻する可能性がある。

既にありがちなんだけどまあ Expression の練習と思って。
オブジェクト構築のコストなんぞ、知らん。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2