LoginSignup
3
4

More than 5 years have passed since last update.

Expression Tree プロパティマッパー

Last updated at Posted at 2017-07-31

初投稿(ブログすらない)なのであまり上手く書けませんが、間違い等があればご指摘頂きたいです…

プロパティをGetValueとかするリフレクションが遅かったので、その改善策を考えてみました。


private void Update(object model,List<PropertyInfo> query)
{
    if (model == null)
        return;

    query.ForEach(i =>
    {
        PropertyInfo prop = model.GetType().GetProperty(i.Name);
        if (prop != null && prop.PropertyType == i.PropertyType && prop.CanRead)
        {
            var val = prop.GetValue(model, null);
            i.SetValue(view, val, null);
        }
    });
}

こんな感じでEntityとViewModelの詰め替えとかによく使ってました。
AutoMapperとかもあるけど…なんだか好きになれないので…

ただ100万回foreachでぶん回すとかすると遅いのでExpression Treeでマッパーを作ってみました。(Webアプリ100万回ぶん回す処理とかはないかな)

正直なところExpression Treeを完全に理解できてないので拙いコードになってる気がします。


    /// <summary>
    /// Propertyマッパー
    /// </summary>
    public class PropertyMapper
    {
        /// <summary>
        /// デフォルトコンストラクタ
        /// </summary>
        private PropertyMapper()
        {
        }

        /// <summary>
        /// 型:TModel の CanRead なプロパティの値を 型:TEntity の対応する CanWrite なプロパティの値へ設定します。
        /// 設定可能なプロパティは PropertyName と PropertyType が同一でなければなりません。
        /// </summary>
        /// <typeparam name="TModel">設定元タイプ</typeparam>
        /// <typeparam name="TEntity">設定先タイプ</typeparam>
        /// <param name="items">IEnmerableを実装した TModel</param>
        /// <returns>IEnumerableを実装した TEntity</returns>
        public static IEnumerable<TEntity> Mapper<TModel, TEntity>(IEnumerable<TModel> items)
            where TModel : class, new()
            where TEntity : class, new()
        {
            // 戻り値
            var entities = new List<TEntity>();

            // Getアクセッサ デリゲートキャッシュ
            var getterDelegateItems = new Dictionary<int, Func<TModel, object>>();

            // Setアクセッサ デリゲートキャッシュ
            var setterDelegateItems = new Dictionary<int, Action<TEntity, object>>();

            // Getアクセッサ デリゲート
            Func<TModel, object> getter = null;

            // Setアクセッサ デリゲート
            Action<TEntity, object> setter = null;

            // 設定対象プロパティリスト
            var entityTargetProps = new List<PropertyInfo>();

            // TEntityプロパティリスト ディクショナリ(パフォーマンス対策)
            var entityProps = typeof(TEntity).GetProperties().Where(a => a.CanWrite).ToDictionary(a => a.Name);

            // TModel プロパティリスト
            var modelProps = typeof(TModel).GetProperties().Where(a => a.CanRead);

            // 設定対象プロパティを抽出
            PropertyInfo entityProp = null;
            foreach (var modelProp in modelProps)
            {
                // TEntityプロパティリストに TModelプロパティと同一の名前で存在する、且つ、型が完全一致する場合
                if (entityProps.TryGetValue(modelProp.Name, out entityProp) && entityProp.PropertyType == modelProp.PropertyType)
                {
                    // 設定対象プロパティに追加
                    entityTargetProps.Add(entityProp);
                }
            }

            // プロパティ設定
            foreach (var item in items)
            {
                // 設定先TEntity
                var entity = new TEntity();

                // 設定対象プロパティ
                foreach (var setProp in entityTargetProps)
                {
                    // キャッシュされていないかどうかを判定し、未キャッシュの場合は生成
                    if (!getterDelegateItems.TryGetValue(setProp.MetadataToken, out getter))
                    {
                        // Getアクセッサ デリゲートを生成
                        getter = CreateGetDelegate<TModel>(setProp.Name);

                        // 生成したGetデリゲートをキャッシュ
                        getterDelegateItems.Add(setProp.MetadataToken, getter);

                        // Setアクセッサ デリゲートを生成
                        setter = CreateSetDelegate<TEntity>(setProp.Name);

                        // 生成したSetデリゲートをキャッシュ
                        setterDelegateItems.Add(setProp.MetadataToken, setter);
                    }

                    setter = setterDelegateItems[setProp.MetadataToken];

                    // プロパティ値を取得
                    var val = getter(item);

                    // プロパティ値を設定
                    setter(entity, val);
                }

                entities.Add(entity);
            }

            return entities;
        }

        /// <summary>
        /// Setアクセッサ デリゲートを生成します
        /// </summary>
        /// <typeparam name="TModel">プロパティ値が設定されるオブジェクト型</typeparam>
        /// <param name="memberName">設定するプロパティ名</param>
        /// <returns>Setアクセッサ デリゲート</returns>
        private static Action<TModel, object> CreateSetDelegate<TModel>(string memberName)
        {
            var target = Expression.Parameter(typeof(TModel), "target");
            var value = Expression.Parameter(typeof(object), "value");

            var left = Expression.PropertyOrField(target, memberName);
            var right = Expression.Convert(value, left.Type);

            var lambda = Expression.Lambda<Action<TModel, object>>(
                            Expression.Assign(left, right),
                            target,
                            value);

            return lambda.Compile();
        }

        /// <summary>
        /// Getアクセッサ デリゲートを生成します
        /// </summary>
        /// <typeparam name="TModel">プロパティ値が返されるオブジェクト型</typeparam>
        /// <param name="propertyName">取得するプロパティ名</param>
        /// <returns>Getアクセッサ デリゲート</returns>
        private static Func<TModel, object> CreateGetDelegate<TModel>(string propertyName)
        {
            var target = Expression.Parameter(typeof(TModel), "target");

            var lambda = Expression.Lambda<Func<TModel, object>>(
                            Expression.Convert(
                                Expression.PropertyOrField(target, propertyName),
                                typeof(object)),
                            target);

            return lambda.Compile();
        }
    }
Non Expression Tree Expression Tree
1回目 2820ms 463ms
2回目 2849ms 470ms
3回目 2924ms 614ms
4回目 3527ms 516ms

一応速くなってます。

3
4
1

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
3
4