Loading

[C#] 动态编程表达式树实现NPOI.IROW快速映射优化

环境

.Net core 3.1
c# 9.0

目的

实现NPOI IROW和Object的自动转换,无需手写转换代码

原理

  • 原始对象转换最高效,但是对每个要映射的对象都手写代码,不仅不利于阅读,同时造成大量冗余代码
  • 通过第一次对构造转换函数并缓存,实现逼近原始手写代码的转换效率,同时又可以根据runtime不同的对象进行转换
  • 表达式树相对于旧版本使用的EMIT直接编写IL来说,更加易于阅读

转换对象

 public class Test
 {
        public Guid Id { get; set; }

        public DateTime Time { get; set; }

        public DateTime Time2 { get; set; }

        public DateTime Time3 { get; set; }

        public DateTime Time4 { get; set; }

        public List<string> Description { get; set; }
}

三种转换代码对比

原始转换代码:

        public static void SaveTest(List<Test> tests)
        {
            var book = new NPOI.XSSF.UserModel.XSSFWorkbook();
            ISheet sheet = book.CreateSheet("Sheet");
            IRow row = sheet.CreateRow(0);
            var type = typeof(Test);
            var props = type.GetProperties();
            var index = 0;
            foreach (var prop in props)
                row.CreateCell(index++).SetCellValue(prop.Name);
            var rowIndex = 1;
            foreach (var test in tests)
            {
                row = sheet.CreateRow(rowIndex++);
                row.CreateCell(0).SetCellValue(test.Id.ToString());
                row.CreateCell(1).SetCellValue(test.Time.ToString());
                row.CreateCell(2).SetCellValue(test.Time2.ToString());
                row.CreateCell(3).SetCellValue(test.Time3.ToString());
                row.CreateCell(4).SetCellValue(test.Time4.ToString());
            }
            using var file = new FileStream(Path.Join(AppContext.BaseDirectory, $"./testorigin.xlsx"), FileMode.OpenOrCreate);
            book.Write(file);
        }

反射代码:

        public static IRow Parse<T>(this IRow row, T data)
        {
            var type = typeof(T);
            var props = type.GetProperties();
            for (var index = 0; index < props.Length; ++index)
            {
                var prop = props[index];
                var value = prop.GetValue(data);
                if (value != null)
                    row.CreateCell(index).SetCellValue(value.ToString());
            }
            return row;
        }

        public static void SaveRef<T>(List<T> tests) 
        {
            var book = new NPOI.XSSF.UserModel.XSSFWorkbook();
            ISheet sheet = book.CreateSheet("Sheet");
            IRow row = sheet.CreateRow(0);
            var type = typeof(T);
            var props = type.GetProperties();
            var index = 0;
            foreach (var prop in props)
                row.CreateCell(index++).SetCellValue(prop.Name);
            var rowIndex = 1;
            foreach (var test in tests)
            {
                row = sheet.CreateRow(rowIndex++);
                row.Parse(test);
            }
            using var file = new FileStream(Path.Join(AppContext.BaseDirectory, $"./test3.xlsx"), FileMode.OpenOrCreate);
            book.Write(file);
        }

表达式树代码

        public static Action<IRow, T> CreateFunc<T>() where T : new()
        {
            var typeRow = typeof(IRow);
            var typeData = typeof(T);
            var body = new List<Expression>();
            var para1 = Expression.Parameter(typeRow, "row");
            var para2 = Expression.Parameter(typeData, "object");
            var props = typeData.GetProperties();
            var createCell = typeRow.GetMethod("CreateCell", new[] { typeof(int) });
            var setCellValue = typeof(ICell).GetMethod("SetCellValue", new[] { typeof(string) });
            var index = 0;
            foreach (var prop in props)
            {
                var toStringMethod = prop.PropertyType.GetMethod("ToString", new Type[] { });
                if (toStringMethod != null)
                {
                    var propEx = Expression.Property(para2, prop.Name);
                    var stringValue = Expression.Call(propEx, toStringMethod);
                    var cell = Expression.Call(para1, createCell, Expression.Constant(index++));
                    var setCell = Expression.Call(cell, setCellValue, stringValue);
                    var ifthen = Expression.IfThen(Expression.NotEqual(propEx, Expression.Default(prop.PropertyType)), setCell);
                    body.Add(ifthen);
                }
            }
            var lambda = Expression.Lambda<Action<IRow, T>>(Expression.Block(body.ToArray()), para1, para2);
            return lambda.Compile();
        }

        public static void Save<T>(List<T> tests) where T : new()
        {
            var book = new NPOI.XSSF.UserModel.XSSFWorkbook();
            ISheet sheet = book.CreateSheet("Sheet");
            IRow row = sheet.CreateRow(0);
            var type = typeof(T);
            var props = type.GetProperties();
            var index = 0;
            foreach (var prop in props)
                row.CreateCell(index++).SetCellValue(prop.Name);
            var rowIndex = 1;
            Action<IRow, T> convert = CreateFunc<T>();
            foreach (var test in tests)
            {
                row = sheet.CreateRow(rowIndex++);
                convert(row, test);
            }
            using var file = new FileStream(Path.Join(AppContext.BaseDirectory, $"./test2.xlsx"), FileMode.OpenOrCreate);
            book.Write(file);
        }

通过BenchmarkDotnet进行测试
测试代码

    public class NpoiTest
    {
        private readonly List<Test> _tests;

        public NpoiTest()
        {
            var cnt = 100000;
            _tests = new List<Test>();
            for (var i = 0; i < cnt; ++i)
            {
                _tests.Add(
                   new Test { Id = Guid.NewGuid(), Time = DateTime.Now, Time2 = DateTime.Now, Time3 = DateTime.Now, Time4 = DateTime.Now }
               );
            }
        }

        [Benchmark]
        public void OriginParse() => NpoiParse.Npoiparse.SaveTest(_tests);

        [Benchmark]
        public void ExpressionParse() => NpoiParse.Npoiparse.Save(_tests);

        [Benchmark]
        public void RefectParse() => NpoiParse.Npoiparse.SaveRef(_tests);
        
    }

测试结果
100000数量级

这里反射的代码其实很少,与原始mapper效率并没有区别很大

posted @ 2021-03-03 22:03  minskiter  阅读(358)  评论(0编辑  收藏  举报