利用表达式树Expression优化反射性能

最近做了一个.Net Core环境下,基于NPOI的Excel导入导出以及Word操作的服务封装,
涉及到大量反射操作,在性能优化过程中使用到了表达式树,记录一下。

Excel导入是相对比较麻烦的一块,实现的效果是:调用方只需要定义一个类,只需要标记特性,
服务读取Excel=>校验(正则、必填、整数范围、日期、数据库是否存在、数据重复) =>将校验结果返回 => 提供方法将Excel数据
转换为指定类集合。

在最后一步转换,最开始用反射实现,性能较差;后来通过了反射+委托,表达式树方式进行优化,
最终性能接近了硬编码。见图,转换近5000条有效数据,耗时仅100毫秒不到,是反射的近20倍。

 

读取Excel数据之后,我将数据读取到了自定义的两个类(方便后面的校验)

 

   public class ExcelDataRow
    {
        /// <summary>
        /// 行号
        /// </summary>
        public int RowIndex { get; set; }

        /// <summary>
        /// 单元格数据
        /// </summary>
        public List<ExcelDataCol> DataCols { get; set; } = new List<ExcelDataCol>();

        /// <summary>
        /// 是否有效
        /// </summary>
        public bool IsValid { get; set; }

        /// <summary>
        /// 错误信息
        /// </summary>
        public string ErrorMsg { get; set; }
    }

    public class ExcelDataCol : ExcelCol
    {
        /// <summary>
        /// 对应属性名称
        /// </summary>
        public string PropertyName { get; set; }

        /// <summary>
        /// 行号
        /// </summary>
        public int RowIndex { get; set; }

        /// <summary>
        /// 字符串值
        /// </summary>
        public string ColValue { get; set; }
    }

 

校验完之后,需要将ExcelDataRow转换为指定类型

 

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Ade.OfficeService.Excel
{
    /// <summary>
    /// 生成表达式目录树 缓存
    /// </summary>
    public class ExpressionMapper
    {
        private static Hashtable Table = Hashtable.Synchronized(new Hashtable(1024));

        /// <summary>
        /// 将ExcelDataRow快速转换为指定类型
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dataRow"></param>
        /// <returns></returns>
        public static T FastConvert<T>(ExcelDataRow dataRow)
        {
            //利用表达式树,动态生成委托并缓存,得到接近于硬编码的性能
            //最终生成的代码近似于(假设T为Person类)
            //Func<ExcelDataRow,Person>
            //      new Person(){
            //          Name = Convert(ChangeType(dataRow.DataCols.SingleOrDefault(c=>c.PropertyName == prop.Name).ColValue,prop.PropertyType),prop.ProertyType),
            //          Age = Convert(ChangeType(dataRow.DataCols.SingleOrDefault(c=>c.PropertyName == prop.Name).ColValue,prop.PropertyType),prop.ProertyType)
            //      }
            // }

            string propertyNames = string.Empty;
            dataRow.DataCols.ForEach(c => propertyNames += c.PropertyName + "_");
            var key = typeof(T).FullName + "_" + propertyNames.Trim('_');


            if (!Table.ContainsKey(key))
            {
                List<MemberBinding> memberBindingList = new List<MemberBinding>();

                MethodInfo singleOrDefaultMethod = typeof(Enumerable)
                                                            .GetMethods()
                                                            .Single(m => m.Name == "SingleOrDefault" && m.GetParameters().Count() == 2)
                                                            .MakeGenericMethod(new[] { typeof(ExcelDataCol) });

                foreach (var prop in typeof(T).GetProperties())
                {
                    Expression<Func<ExcelDataCol, bool>> lambdaExpr = c => c.PropertyName == prop.Name;

                    MethodInfo changeTypeMethod = typeof(ExpressionMapper).GetMethods().Where(m => m.Name == "ChangeType").First();

                    Expression expr =
                        Expression.Convert(
                            Expression.Call(changeTypeMethod
                                , Expression.Property(
                                    Expression.Call(
                                          singleOrDefaultMethod
                                        , Expression.Constant(dataRow.DataCols)
                                        , lambdaExpr)
                                        , typeof(ExcelDataCol), "ColValue"), Expression.Constant(prop.PropertyType))
                                    , prop.PropertyType);

                    memberBindingList.Add(Expression.Bind(prop, expr));
                }

                MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(T)), memberBindingList.ToArray());
                Expression<Func<ExcelDataRow, T>> lambda = Expression.Lambda<Func<ExcelDataRow, T>>(memberInitExpression, new ParameterExpression[]
                {
                    Expression.Parameter(typeof(ExcelDataRow), "p")
                });

                Func<ExcelDataRow, T> func = lambda.Compile();//拼装是一次性的
                Table[key] = func;
            }
            var ss = (Func<ExcelDataRow, T>)Table[key];

            return ((Func<ExcelDataRow, T>)Table[key]).Invoke(dataRow);
        }

        public static object ChangeType(string stringValue, Type type)
        {
            object obj = null;

            Type nullableType = Nullable.GetUnderlyingType(type);
            if (nullableType != null)
            {
                if (stringValue == null)
                {
                    obj = null;
                }

            }
            else if (typeof(System.Enum).IsAssignableFrom(type))
            {
                obj = Enum.Parse(type, stringValue);
            }
            else
            {
                obj = Convert.ChangeType(stringValue, type);
            }

            return obj;
        }
    }
}

  

posted @ 2018-11-12 18:30  holdengong  阅读(1279)  评论(0编辑  收藏  举报