• [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效率并没有区别很大

  • 相关阅读:
    [CF603C] Lieges of Legendre
    [CF1070A] Find a Number
    [CF431D] Random Task
    2020牛客暑期多校训练营(第二场)C
    2020牛客暑期多校训练营(第二场)F
    2020牛客暑期多校训练营(第二场)D
    2020牛客暑期多校训练营(第一场)H
    [CF1000E] We Need More Bosses
    Java学习2 (ThreadLocal)
    Java复习1
  • 原文地址:https://www.cnblogs.com/minskiter/p/14477231.html
Copyright © 2020-2023  润新知