利用表达式树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; } } }