C#

Expression Tree プロパティマッパー

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

プロパティを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

一応速くなってます。