C# 對象類型映射轉換方法總結,表達式樹實現高效轉換


對象類型映射轉換常用方法

       開發過程中經常會遇到對象類型之間的轉換映射,例如Model和ViewModel之間的映射綁定,下面總結幾種常見的轉換方式。事先准備兩個類:
CheckFileCheckFileModel


 	public class CheckFile
    {
        public string Id { get; set; }
        public string FileTitle { get; set; }
        public string Author { get; set; }
        public DateTime? CreatTime;
    }
    
    public class CheckFileModel
    {
        public string Id { get; set; }
        public string FileTitle { get; set; }
        public string Author { get; set; }
        public DateTime? CreatTime;
        public string Source { get; set; }
    }

1. 使用強類型賦值綁定

    
    CheckFile checkFile = new CheckFile
    {
        Id = "123",
        FileTitle = "對象類型轉換映射",
        Author = "張三",
        CreatTime = DateTime.Now
    };

	CheckFileModel fileModel = new CheckFileModel
    {
        Id = checkFile.Id,
        FileTitle = checkFile.FileTitle,
        Author = checkFile.Author,
        CreatTime = checkFile.CreatTime
    };

2. 使用泛型+反射


	  /// <summary>
      /// 泛型+反射
      /// </summary>
      /// <typeparam name="TOut"></typeparam>
      /// <typeparam name="TIn"></typeparam>
      /// <param name="objIn"></param>
      /// <returns></returns>
      public static TOut TypeConvert<TOut, TIn>(TIn objIn)
      {
          Type tOut = typeof(TOut);
          Type tIn = typeof(TIn);
          TOut outModel = Activator.CreateInstance<TOut>();

          // 屬性賦值
          foreach (var prop in tOut.GetProperties())
          {
              var propInfo = tIn.GetProperty(prop.Name);
              if (propInfo != null)
              {
                  var inValue = propInfo.GetValue(objIn);
                  prop.SetValue(outModel, inValue);
              }
          }

          // 字段賦值
          foreach (var field in tOut.GetFields())
          {
              var fieldInfo = tIn.GetField(field.Name);
              if (fieldInfo != null)
              {
                  var inValue = fieldInfo.GetValue(objIn);
                  field.SetValue(outModel, inValue);
              }
          }
          return outModel;
      }

3. 使用Json序列化轉換


      /// <summary>
      /// Json序列化轉換
      /// </summary>
      /// <typeparam name="TOut"></typeparam>
      /// <typeparam name="TIn"></typeparam>
      /// <param name="objIn"></param>
      /// <returns></returns>
      public static TOut SerializeConvert<TOut, TIn>(TIn objIn)
      {
          string inString = JsonConvert.SerializeObject(objIn);
          TOut outModel = JsonConvert.DeserializeObject<TOut>(inString);
          return outModel;
      }

4. 使用AutoMapper序列化轉換


      /// <summary>
      /// AutoMapper序列化轉換
      /// </summary>
      /// <typeparam name="TOut"></typeparam>
      /// <typeparam name="TIn"></typeparam>
      /// <param name="objIn"></param>
      /// <returns></returns>
      public static TOut AutoMapperConvert<TOut, TIn>(TIn objIn)
      {
          // 初始化
          Mapper.Initialize(n => n.CreateMap<TIn, TOut>());

          TOut outModel = Mapper.Map<TIn, TOut>(objIn);
          return outModel;
      }

5. 使用Expression表達式目錄樹轉換


      /// <summary>
      /// Expression表達式目錄樹轉換
      /// </summary>
      /// <typeparam name="TOut"></typeparam>
      /// <typeparam name="TIn"></typeparam>
      /// <param name="objIn"></param>
      /// <returns></returns>
      public static TOut ExpressionConvert<TOut, TIn>(TIn objIn)
      {
          ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
          List<MemberBinding> memberBindingList = new List<MemberBinding>();

          // 綁定屬性
          PropertyInfo[] outPropertyInfos = typeof(TOut).GetProperties();
          foreach (var prop in outPropertyInfos)
          {
              PropertyInfo inPropertyInfo = typeof(TIn).GetProperty(prop.Name);
              if (inPropertyInfo != null)
              {
                  MemberExpression property = Expression.Property(parameterExpression, inPropertyInfo);
                  MemberBinding memberBinding = Expression.Bind(prop, property);
                  memberBindingList.Add(memberBinding);
              }
          }

          // 綁定字段
          FieldInfo[] outFieldInfos = typeof(TOut).GetFields();
          foreach (var field in outFieldInfos)
          {
              FieldInfo inFieldInfo = typeof(TIn).GetField(field.Name);
              if (inFieldInfo != null)
              {
                  MemberExpression fieldInfo = Expression.Field(parameterExpression, inFieldInfo);
                  MemberBinding memberBinding = Expression.Bind(field, fieldInfo);
                  memberBindingList.Add(memberBinding);
              }
          }

          MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
          Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
          {
              parameterExpression
          });
          Func<TIn, TOut> func = lambda.Compile();
          return func.Invoke(objIn);
      }
	  
	  //----------------------------------------------------------
	  // 等價於構造一個下面的表達式
      Expression<Func<CheckFile, CheckFileModel>> lambda = p => new CheckFileModel
      {
          Id = p.Id,
          FileTitle = p.FileTitle,
          Author = p.Author,
          CreatTime = p.CreatTime
      };
      lambda.Compile().Invoke(checkFile);

表達式目錄樹介紹

在這里插入圖片描述
每個矩形框為一個節點(表達式類型),節點有多種類型,對於而這個 a*b+2 的幾個節點:

  • a,b是參數,類型為ParameterExpression
  • +,*,為二元運符,類型為BinaryExpression
  • 2為常量,類型為ConstantExpression

	Expression<Func<int, int, int>> exp1 = (a, b) => a * b+2;

	//兩個參數
	ParameterExpression a = Expression.Parameter(typeof(int), "a");
	ParameterExpression b = Expression.Parameter(typeof(int), "b"); 
	
	//求積
	BinaryExpression Multi=Expression.Multiply(a,b); 
	
	//常量
	ConstantExpression x2 = Expression.Constant(2); 
	
	//求和
	BinaryExpression Add = Expression.Add(Multi, x2); 
	
	//創建一個表示lambda表達式的對象
	LambdaExpression lexp = Expression.Lambda<Func<int, int, int>>(Add, a, b); 

	//查看表達式
	Console.WriteLine(lexp.ToString());
	
	//簡單使用
	Expression<Func<int, int, int>> lexp = Expression.Lambda<Func<int, int, int>>(Add, a, b);
	Func<int, int, int> fun = lexp.Compile();
	Console.WriteLine(fun(3,5));

出處:https://blog.csdn.net/qq_31176861/article/details/86551996

=======================================================================================

C# 高性能對象映射(表達式樹實現)

前言

  上篇簡單實現了對象映射,針對數組,集合,嵌套類並沒有給出實現,這一篇繼續完善細節。

 

開源對象映射類庫映射分析

 1.AutoMapper 

   實現原理:主要通過表達式樹Api 實現對象映射 

   優點: .net功能最全的對象映射類庫。

   缺點:當出現復雜類型和嵌套類型時性能直線下降,甚至不如序列化快

 2.TinyMapper

   實現原理:主要通過Emit 實現對象映射 

   優點:速度非常快。在處理復雜類型和嵌套類型性能也很好

   缺點:相對AutoMapper功能上少一些,Emit的實現方案,在代碼閱讀和調試上相對比較麻煩,而表達式樹直接觀察 DebugView中生成的代碼結構便可知道問題所在

 

 3. 本文的對象映射庫

  針對AutoMapper 處理復雜類型和嵌套類型時性能非常差的情況,自己實現一個表達式樹版的高性能方案

 

實現對象映射庫的過程

  構造測試類

    public class TestA
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public TestC TestClass { get; set; }
        public IEnumerable<TestC> TestLists { get; set; }
    }

    public class TestB
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public TestD TestClass { get; set; }
        public TestD[] TestLists { get; set; }
    }

    public class TestC
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public TestC SelfClass { get; set; }
    }

    public class TestD
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public TestD SelfClass { get; set; }

    }

 

 

 1.初步實現

    利用表達式樹給屬性賦值 利用 Expresstion.New構造 var b=new B{};

        public static Func<TSource, TTarget> GetMap<TSource, TTarget>()
        {
            var sourceType = typeof(TSource);
            var targetType = typeof(TTarget);

            //構造 p=>
            var parameterExpression = Expression.Parameter(sourceType, "p");

            //構造 p=>new TTarget{ Id=p.Id,Name=p.Name };
            var memberBindingList = new List<MemberBinding>();
            foreach (var sourceItem in sourceType.GetProperties())
            {
                var targetItem = targetType.GetProperty(sourceItem.Name);
                if (targetItem == null || sourceItem.PropertyType != targetItem.PropertyType)
                    continue;

                var property = Expression.Property(parameterExpression, sourceItem);
                var memberBinding = Expression.Bind(targetItem, property);
                memberBindingList.Add(memberBinding);
            }
            var memberInitExpression = Expression.MemberInit(Expression.New(targetType), memberBindingList);

            var lambda = Expression.Lambda<Func<TSource, TTarget>>(memberInitExpression, parameterExpression);

            Console.WriteLine("lambda: " + lambda);
            return lambda.Compile();
        }

 

  調用如下

     class Program
     {
         static void Main(string[] args)
         {
             var testA = new TestA { Id = 1, Name = "張三" };
             var func = TestClass.ExpresstionHelper.GetMap<TestA, TestB>();
             TestB testB = func(testA);
             Console.WriteLine($"testB.Id={testB.Id},testB.Name={testB.Name}");
             Console.ReadLine();
         }
     }

 

 

  輸出結果

 

總結:此方法需要調用前需要手動編譯下,然后再調用委托沒有緩存委托,相對麻煩。

2.緩存實現

   利用靜態泛型類緩存泛型委托

    public class DataMapper<TSource, TTarget>
    {
        private static Func<TSource, TTarget> MapFunc { get; set; }

        public static TTarget Map(TSource source)
        {
            if (MapFunc == null)
                MapFunc = ExpresstionHelper.Map<TSource, TTarget>();//方法在上邊
            return MapFunc(source);
        }
    }

 

 

   調用方法

        static void Main(string[] args)
        {
            var testA2 = new TestA { Id = 2, Name = "張三2" };
            TestB testB2 = DataMapper<TestA, TestB>.Map(testA2);//委托不存在時自動生成,存在時調用靜態緩存
            Console.WriteLine($"testB.Id={testB2.Id},testB.Name={testB2.Name}");
            Console.ReadLine();
        }

 

 

 輸出結果

 

  總結:引入靜態泛型類能解決泛型委托緩存提高性能,但是有兩個問題  1.當傳入參數為null時 則會拋出空引用異常 2.出現復雜類型上述方法便不能滿足了

 

3.解決參數為空值和復雜類型的問題

    首先先用常規代碼實現下帶有復雜類型賦值的情況

復制代碼
 1 public TestB GetTestB(TestA testA)
 2         {
 3             TestB testB;
 4             if (testA != null)
 5             {
 6                 testB = new TestB();
 7                 testB.Id = testA.Id;
 8                 testB.Name = testA.Name;
 9                 if (testA.TestClass != null)
10                 {
11                     testB.TestClass = new TestD();
12                     testB.TestClass.Id = testA.TestClass.Id;
13                     testB.TestClass.Name = testA.TestClass.Name;
14                 }
15             }
16             else
17             {
18                 testB = null;
19             }
20             return testB;
21         }
復制代碼

  將上面的代碼翻譯成表達式樹

        public static Func<TSource, TTarget> Map<TSource, TTarget>()
        {
            var sourceType = typeof(TSource);
            var targetType = typeof(TTarget);

            //Func委托傳入變量
            var parameter = Expression.Parameter(sourceType);
            //聲明一個返回值變量
            var variable = Expression.Variable(targetType);
            //創建一個if條件表達式
            var test = Expression.NotEqual(parameter, Expression.Constant(null, sourceType));// p==null;
            var ifTrue = Expression.Block(GetExpression(parameter, variable, sourceType, targetType));
            var IfThen = Expression.IfThen(test, ifTrue);

            //構造代碼塊
            var block = Expression.Block(new[] { variable }, parameter, IfThen, variable);

            var lambda = Expression.Lambda<Func<TSource, TTarget>>(block, parameter);
            return lambda.Compile();
        }

        private static List<Expression> GetExpression(Expression parameter, Expression variable, Type sourceType, Type targetType)
        {
            //創建一個表達式集合
            var expressions = new List<Expression>();

            expressions.Add(Expression.Assign(variable, Expression.MemberInit(Expression.New(targetType))));

            foreach (var targetItem in targetType.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite))
            {
                var sourceItem = sourceType.GetProperty(targetItem.Name);

                //判斷實體的讀寫權限
                if (sourceItem == null || !sourceItem.CanRead || sourceItem.PropertyType.IsNotPublic)
                    continue;

                var sourceProperty = Expression.Property(parameter, sourceItem);
                var targetProperty = Expression.Property(variable, targetItem);

                //判斷都是class 且類型不相同時
                if (targetItem.PropertyType.IsClass && sourceItem.PropertyType.IsClass && targetItem.PropertyType != sourceItem.PropertyType)
                {
                    if (targetItem.PropertyType != targetType)//不處理嵌套循環的情況
                    {
                        //由於類型是class 所以默認值是null
                        var testItem = Expression.NotEqual(sourceProperty, Expression.Constant(null, sourceItem.PropertyType));

                        var itemExpressions = GetExpression(sourceProperty, targetProperty, sourceItem.PropertyType, targetItem.PropertyType);
                        var ifTrueItem = Expression.Block(itemExpressions);

                        var IfThenItem = Expression.IfThen(testItem, ifTrueItem);
                        expressions.Add(IfThenItem);

                        continue;
                    }
                }

                //目標值類型時 且兩者類型不一致時跳過
                if (targetItem.PropertyType != sourceItem.PropertyType)
                    continue;

                expressions.Add(Expression.Assign(targetProperty, sourceProperty));
            }

            return expressions;
        }

 

總結:此方案,運用 Expression.IfThen(testItem, ifTrueItem) 判斷空值問題,通過遞歸調用 GetExpression()方法,處理復雜類型。

但是:針對嵌套類仍然不能解決。因為表達式樹是在實際調用方法之前就生成的,在沒有實際的參數值傳入之前,生成的表達式是不知道有多少層級的。

           有個比較low的方案是,預先設定嵌套層級為10層,然后生成一個有10層 if(P!=null) 的判斷。如果傳入的參數層級超過10層了呢,就得手動調整生成的樹,此方案也否決。

最后得出的結論只能在表達式中動態調用方法。

4.最終版本

   通過動態調用方法解決嵌套類,代碼如下

    using System.ComponentModel.DataAnnotations.Schema;
    using System.Linq.Expressions;
    using System.Reflection;
    using static System.Linq.Expressions.Expression;

    public static class ExprMapper<TSource, TTarget> where TSource : class where TTarget : class
    {
        private readonly static Func<TSource, TTarget> MapFunc = GetMapFunc();

        private readonly static Action<TSource, TTarget> MapAction = GetMapAction();

        /// <summary>
        /// 將對象TSource轉換為TTarget
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static TTarget Map(TSource source) => MapFunc(source);

        /// <summary>
        /// 將對象TSource的值賦給給TTarget
        /// </summary>
        /// <param name="source"></param>
        /// <param name="target"></param>
        public static void Map(TSource source, TTarget target) => MapAction(source, target);

        private static List<TTarget> MapList(IEnumerable<TSource> sources) => sources.Select(MapFunc).ToList();


        private static Func<TSource, TTarget> GetMapFunc()
        {
            var sourceType = typeof(TSource);
            var targetType = typeof(TTarget);
            //Func委托傳入變量
            var parameter = Parameter(sourceType, "p");

            var memberBindings = new List<MemberBinding>();
            var targetTypes = targetType.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite);
            foreach (var targetItem in targetTypes)
            {
                var sourceItem = sourceType.GetProperty(targetItem.Name);

                //判斷實體的讀寫權限
                if (sourceItem == null || !sourceItem.CanRead || sourceItem.PropertyType.IsNotPublic)
                    continue;

                //標注NotMapped特性的屬性忽略轉換
                if (sourceItem.GetCustomAttribute<NotMappedAttribute>() != null)
                    continue;

                var sourceProperty = Property(parameter, sourceItem);

                //當非值類型且類型不相同時
                if (!sourceItem.PropertyType.IsValueType && sourceItem.PropertyType != targetItem.PropertyType)
                {
                    //判斷都是(非泛型)class
                    if (sourceItem.PropertyType.IsClass && targetItem.PropertyType.IsClass &&
                        !sourceItem.PropertyType.IsGenericType && !targetItem.PropertyType.IsGenericType)
                    {
                        var expression = GetClassExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                        memberBindings.Add(Bind(targetItem, expression));
                    }

                    //集合數組類型的轉換
                    if (typeof(IEnumerable<>).IsAssignableFrom(sourceItem.PropertyType) && typeof(IEnumerable<>).IsAssignableFrom(targetItem.PropertyType))
                    {
                        var expression = GetListExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                        memberBindings.Add(Bind(targetItem, expression));
                    }

                    continue;
                }

                if (targetItem.PropertyType != sourceItem.PropertyType)
                    continue;

                memberBindings.Add(Bind(targetItem, sourceProperty));
            }

            //創建一個if條件表達式
            var test = NotEqual(parameter, Constant(null, sourceType));// p==null;
            var ifTrue = MemberInit(New(targetType), memberBindings);
            var condition = Condition(test, ifTrue, Constant(null, targetType));

            var lambda = Lambda<Func<TSource, TTarget>>(condition, parameter);
            return lambda.Compile();
        }

        private static Action<TSource, TTarget> GetMapAction()
        {
            var sourceType = typeof(TSource);
            var targetType = typeof(TTarget);
            //Func委托傳入變量
            var sourceParameter = Parameter(sourceType, "p");

            var targetParameter = Parameter(targetType, "t");

            //創建一個表達式集合
            var expressions = new List<Expression>();

            var targetTypes = targetType.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite);
            foreach (var targetItem in targetTypes)
            {
                var sourceItem = sourceType.GetProperty(targetItem.Name);

                //判斷實體的讀寫權限
                if (sourceItem == null || !sourceItem.CanRead || sourceItem.PropertyType.IsNotPublic)
                    continue;

                //標注NotMapped特性的屬性忽略轉換
                if (sourceItem.GetCustomAttribute<NotMappedAttribute>() != null)
                    continue;

                var sourceProperty = Property(sourceParameter, sourceItem);
                var targetProperty = Property(targetParameter, targetItem);

                //當非值類型且類型不相同時
                if (!sourceItem.PropertyType.IsValueType && sourceItem.PropertyType != targetItem.PropertyType)
                {
                    //判斷都是(非泛型)class
                    if (sourceItem.PropertyType.IsClass && targetItem.PropertyType.IsClass &&
                        !sourceItem.PropertyType.IsGenericType && !targetItem.PropertyType.IsGenericType)
                    {
                        var expression = GetClassExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                        expressions.Add(Assign(targetProperty, expression));
                    }

                    //集合數組類型的轉換
                    if (typeof(IEnumerable<>).IsAssignableFrom(sourceItem.PropertyType) && typeof(IEnumerable<>).IsAssignableFrom(targetItem.PropertyType))
                    {
                        var expression = GetListExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                        expressions.Add(Assign(targetProperty, expression));
                    }

                    continue;
                }

                if (targetItem.PropertyType != sourceItem.PropertyType)
                    continue;


                expressions.Add(Assign(targetProperty, sourceProperty));
            }

            //當Target!=null判斷source是否為空
            var testSource = NotEqual(sourceParameter, Constant(null, sourceType));
            var ifTrueSource = Block(expressions);
            var conditionSource = IfThen(testSource, ifTrueSource);

            //判斷target是否為空
            var testTarget = NotEqual(targetParameter, Constant(null, targetType));
            var conditionTarget = IfThen(testTarget, conditionSource);

            var lambda = Lambda<Action<TSource, TTarget>>(conditionTarget, sourceParameter, targetParameter);
            return lambda.Compile();
        }

        /// <summary>
        /// 類型是clas時賦值
        /// </summary>
        /// <param name="sourceProperty"></param>
        /// <param name="targetProperty"></param>
        /// <param name="sourceType"></param>
        /// <param name="targetType"></param>
        /// <returns></returns>
        private static Expression GetClassExpression(Expression sourceProperty, Type sourceType, Type targetType)
        {
            //條件p.Item!=null    
            var testItem = NotEqual(sourceProperty, Constant(null, sourceType));

            //構造回調 ExprMapper<TSource, TTarget>.Map()
            var mapperType = typeof(ExprMapper<,>).MakeGenericType(sourceType, targetType);
            var iftrue = Call(mapperType.GetMethod(nameof(Map), new[] { sourceType }), sourceProperty);

            var conditionItem = Condition(testItem, iftrue, Constant(null, targetType));

            return conditionItem;
        }

        /// <summary>
        /// 類型為集合時賦值
        /// </summary>
        /// <param name="sourceProperty"></param>
        /// <param name="targetProperty"></param>
        /// <param name="sourceType"></param>
        /// <param name="targetType"></param>
        /// <returns></returns>
        private static Expression GetListExpression(Expression sourceProperty, Type sourceType, Type targetType)
        {
            //條件p.Item!=null    
            var testItem = NotEqual(sourceProperty, Constant(null, sourceType));

            //構造回調 ExprMapper<TSource, TTarget>.MapList()
            var sourceArg = sourceType.IsArray ? sourceType.GetElementType() : sourceType.GetGenericArguments()[0];
            var targetArg = targetType.IsArray ? targetType.GetElementType() : targetType.GetGenericArguments()[0];
            var mapperType = typeof(ExprMapper<,>).MakeGenericType(sourceArg, targetArg);

            var mapperExecMap = Call(mapperType.GetMethod(nameof(MapList), new[] { sourceType }), sourceProperty);

            Expression iftrue;
            if (targetType == mapperExecMap.Type)
            {
                iftrue = mapperExecMap;
            }
            else if (targetType.IsArray)//數組類型調用ToArray()方法
            {
                iftrue = Call(mapperExecMap, mapperExecMap.Type.GetMethod("ToArray"));
            }
            else if (typeof(IDictionary<,>).IsAssignableFrom(targetType))
            {
                iftrue = Constant(null, targetType);//字典類型不轉換
            }
            else
            {
                iftrue = Convert(mapperExecMap, targetType);
            }

            var conditionItem = Condition(testItem, iftrue, Constant(null, targetType));

            return conditionItem;
        }

    }
View Code

 

輸出的 表達式

格式化后

p => IIF((p != null),
     new TestB()
     {
       Id = p.Id,
       Name = p.Name,
       TestClass = IIF(
                   (p.TestClass != null),
                    Map(p.TestClass),
                    null
                    ),
       TestLists = IIF(
                     (p.TestLists != null),
                      MapList(p.TestLists).ToArray(),
                      null
                     )
       },
       null)

 

 

說明 Map(p.TestClass)   MapList(p.TestLists).ToArray(),  完整的信息為 Mapper<TestC,TestD>.Map()   Mapper<TestC,TestD>.MapList()  

總結:解決嵌套類的核心代碼

101             //構造回調 Mapper<TSource, TTarget>.Map()
102             var mapperType = typeof(DataMapper<,>).MakeGenericType(sourceType, targetType);
103             var mapperExecMap = Expression.Call(mapperType.GetMethod(nameof(Map), new[] { sourceType }), sourceProperty);

利用Expression.Call  根據參數類型動態生成 對象映射的表達式

 

性能測試

   寫了這么多最終目的還是為了解決性能問題,下面將對比下性能

1.測試類

    using System.Diagnostics;
    using Nelibur.ObjectMapper;

    public static class MapperTest
    {
        public static AutoMapper.IMapper MapperObj = null;
        //執行次數
        public static int Count = 100000;
        public static string StrTime = "";

        //簡單類型
        public static void Nomal()
        {
            
            Console.WriteLine($"******************簡單類型:{Count / 10000}萬次執行時間*****************");
            var model = new TestA
            {
                Id = 1,
                Name = "張三",
            };

            //計時
            var sw = Stopwatch.StartNew();
            for (int i = 0; i < Count; i++)
            {
                if (model != null)
                {
                    var b = new TestB
                    {
                        Id = model.Id,
                        Name = model.Name,
                    };
                }
            }
            sw.Stop();
            Console.WriteLine($"原生的時間:{sw.ElapsedMilliseconds}ms");
            StrTime += $",{sw.ElapsedMilliseconds}";

            Exec(model);
        }

        //復雜類型
        public static void Complex()
        {
            Console.WriteLine($"********************復雜類型:{Count / 10000}萬次執行時間*********************");
            var model = new TestA
            {
                Id = 1,
                Name = "張三",
                TestClass = new TestC
                {
                    Id = 2,
                    Name = "lisi",
                },
            };

            //計時
            var sw = Stopwatch.StartNew();
            for (int i = 0; i < Count; i++)
            {

                if (model != null)
                {
                    var b = new TestB
                    {
                        Id = model.Id,
                        Name = model.Name,
                    };
                    if (model.TestClass != null)
                    {
                        b.TestClass = new TestD
                        {
                            Id = i,
                            Name = "lisi",
                        };
                    }
                }
            }
            sw.Stop();
            Console.WriteLine($"原生的時間:{sw.ElapsedMilliseconds}ms");
            StrTime += $",{sw.ElapsedMilliseconds}";
            Exec(model);
        }

        //嵌套類型
        public static void Nest()
        {
            Console.WriteLine($"*****************嵌套類型:{Count / 10000}萬次執行時間*************************");
            var model = new TestA
            {
                Id = 1,
                Name = "張三",
                TestClass = new TestC
                {
                    Id = 1,
                    Name = "lisi",
                    SelfClass = new TestC
                    {
                        Id = 2,
                        Name = "lisi",
                        SelfClass = new TestC
                        {
                            Id = 3,
                            Name = "lisi",
                            SelfClass = new TestC
                            {
                                Id = 4,
                                Name = "lisi",
                            },
                        },
                    },
                },
            };
            //計時
            var item = model;
            var sw = Stopwatch.StartNew();
            for (int i = 0; i < Count; i++)
            {
                //這里每一步需要做非空判斷的,書寫太麻煩省去了
                if (model != null)
                {
                    var b = new TestB
                    {
                        Id = model.Id,
                        Name = model.Name,
                        TestClass = new TestD
                        {
                            Id = model.TestClass.Id,
                            Name = model.TestClass.Name,
                            SelfClass = new TestD
                            {
                                Id = model.TestClass.SelfClass.Id,
                                Name = model.TestClass.SelfClass.Name,
                                SelfClass = new TestD
                                {
                                    Id = model.TestClass.SelfClass.SelfClass.Id,
                                    Name = model.TestClass.SelfClass.SelfClass.Name,
                                    SelfClass = new TestD
                                    {
                                        Id = model.TestClass.SelfClass.SelfClass.SelfClass.Id,
                                        Name = model.TestClass.SelfClass.SelfClass.SelfClass.Name,
                                    },
                                },
                            },
                        },
                    };
                }
            }
            sw.Stop();
            Console.WriteLine($"原生的時間:{sw.ElapsedMilliseconds}ms");
            StrTime += $",{sw.ElapsedMilliseconds}";

            Exec(model);
        }

        //集合
        public static void List()
        {
            Console.WriteLine($"********************集合類型:{Count / 10000}萬次執行時間***************************");

            var model = new TestA
            {
                Id = 1,
                Name = "張三",
                TestLists = new List<TestC> {
                            new TestC{
                             Id = 1,
                            Name =  "張三",
                           },
                            new TestC{
                            Id = -1,
                            Name =  "張三",
                           },
                        }
            };


            //計時
            var sw = Stopwatch.StartNew();
            for (int i = 0; i < Count; i++)
            {
                var item = model;
                if (item != null)
                {
                    var b = new TestB
                    {
                        Id = item.Id,
                        Name = item.Name,
                        TestLists = new List<TestD> {
                            new TestD{
                                   Id = item.Id,
                            Name = item.Name,
                           },
                            new TestD{
                            Id = -item.Id,
                            Name = item.Name,
                           },
                        }.ToArray()
                    };
                }
            }
            sw.Stop();
            Console.WriteLine($"原生的時間:{sw.ElapsedMilliseconds}ms");
            StrTime += $",{sw.ElapsedMilliseconds}";

            Exec(model);
        }

        public static void Exec(TestA model)
        {
            //表達式
            ExprMapper<TestA, TestB>.Map(model);
            var sw = Stopwatch.StartNew();
            for (int i = 0; i < Count; i++)
            {
                var b = ExprMapper<TestA, TestB>.Map(model);
            }
            sw.Stop();
            Console.WriteLine($"表達式的時間:{sw.ElapsedMilliseconds}ms");
            StrTime += $",{sw.ElapsedMilliseconds}";

            //AutoMapper
            sw.Restart();
            for (int i = 0; i < Count; i++)
            {
                //var b = AutoMapper.Mapper.Map<TestA, TestB>(model);
                var bb = MapperObj.Map<TestB>(model);
            }
            sw.Stop();
            Console.WriteLine($"AutoMapper時間:{sw.ElapsedMilliseconds}ms");
            StrTime += $",{sw.ElapsedMilliseconds}";

            //TinyMapper
            sw.Restart();
            for (int i = 0; i < Count; i++)
            {
                var b = TinyMapper.Map<TestA, TestB>(model);
            }
            sw.Stop();
            Console.WriteLine($"TinyMapper時間:{sw.ElapsedMilliseconds}ms");
            StrTime += $",{sw.ElapsedMilliseconds}";
        }
    }
View Code

 

2.調用測試

        static void TestAllMapper()
        {
            string performanceFile = AppDomain.CurrentDomain.BaseDirectory + "perfReport.csv";
            //AutoMapper.ExprMapper.Initialize(cfg => cfg.CreateMap<TestA, TestB>());
            TestClass.MapperTest.MapperObj = new MapperConfiguration(cfg =>
            {
                cfg.CreateMap<TestA, TestB>();
                cfg.CreateMap<TestC, TestD>();
            }
            ).CreateMapper();
            TinyMapper.Bind<TestA, TestB>();
            TestClass.ExprMapper<TestA, TestB>.Map(new TestA());

            if (!File.Exists(performanceFile))
                new Comm.FileHelper().WriteFile(performanceFile, $"執行次數,原生時間,表達式時間,AutoMapper時間,TinyMapper時間");


            MapperTest.Count = 10000;
            MapperTest.StrTime = $"{ MapperTest.Count / 10000}萬-簡單";
            MapperTest.Nomal();
            new Comm.FileHelper().WriteFile(performanceFile, MapperTest.StrTime, FileMode.Append);
            MapperTest.StrTime = $"{ MapperTest.Count / 10000}萬-復雜";
            MapperTest.Complex();
            new Comm.FileHelper().WriteFile(performanceFile, MapperTest.StrTime, FileMode.Append);
            MapperTest.StrTime = $"{ MapperTest.Count / 10000}萬-嵌套";
            MapperTest.Nest();
            new Comm.FileHelper().WriteFile(performanceFile, MapperTest.StrTime, FileMode.Append);
            MapperTest.StrTime = $"{ MapperTest.Count / 10000}萬-集合";
            MapperTest.List();
            new Comm.FileHelper().WriteFile(performanceFile, MapperTest.StrTime, FileMode.Append);

            MapperTest.Count = 100000;
            MapperTest.StrTime = $"{ MapperTest.Count / 10000}萬-簡單";
            MapperTest.Nomal();
            new Comm.FileHelper().WriteFile(performanceFile, MapperTest.StrTime, FileMode.Append);
            MapperTest.StrTime = $"{ MapperTest.Count / 10000}萬-復雜";
            MapperTest.Complex();
            new Comm.FileHelper().WriteFile(performanceFile, MapperTest.StrTime, FileMode.Append);
            MapperTest.StrTime = $"{ MapperTest.Count / 10000}萬-嵌套";
            MapperTest.Nest();
            new Comm.FileHelper().WriteFile(performanceFile, MapperTest.StrTime, FileMode.Append);
            MapperTest.StrTime = $"{ MapperTest.Count / 10000}萬-集合";
            MapperTest.List();
            new Comm.FileHelper().WriteFile(performanceFile, MapperTest.StrTime, FileMode.Append);

            MapperTest.Count = 1000000;
            MapperTest.StrTime = $"{ MapperTest.Count / 10000}萬-簡單";
            MapperTest.Nomal();
            new Comm.FileHelper().WriteFile(performanceFile, MapperTest.StrTime, FileMode.Append);
            MapperTest.StrTime = $"{ MapperTest.Count / 10000}萬-復雜";
            MapperTest.Complex();
            new Comm.FileHelper().WriteFile(performanceFile, MapperTest.StrTime, FileMode.Append);
            MapperTest.StrTime = $"{ MapperTest.Count / 10000}萬-嵌套";
            MapperTest.Nest();
            new Comm.FileHelper().WriteFile(performanceFile, MapperTest.StrTime, FileMode.Append);
            MapperTest.StrTime = $"{ MapperTest.Count / 10000}萬-集合";
            MapperTest.List();
            new Comm.FileHelper().WriteFile(performanceFile, MapperTest.StrTime, FileMode.Append);

            MapperTest.Count = 10000000;
            MapperTest.StrTime = $"{ MapperTest.Count / 10000}萬-簡單";
            MapperTest.Nomal();
            new Comm.FileHelper().WriteFile(performanceFile, MapperTest.StrTime, FileMode.Append);
            MapperTest.StrTime = $"{ MapperTest.Count / 10000}萬-復雜";
            MapperTest.Complex();
            new Comm.FileHelper().WriteFile(performanceFile, MapperTest.StrTime, FileMode.Append);
            MapperTest.StrTime = $"{ MapperTest.Count / 10000}萬-嵌套";
            MapperTest.Nest();
            new Comm.FileHelper().WriteFile(performanceFile, MapperTest.StrTime, FileMode.Append);
            MapperTest.StrTime = $"{ MapperTest.Count / 10000}萬-集合";
            MapperTest.List();
            new Comm.FileHelper().WriteFile(performanceFile, MapperTest.StrTime, FileMode.Append);


            Console.WriteLine($"------------結束--------------------");
        }
View Code

 

 

3.結果

1萬次

 

10萬次

 

100萬次

 

1000萬次

 

上圖結果AutoMapper 在非簡單類型的轉換上比其他方案有50倍以上的差距,幾乎就跟反射的結果一樣。

另外,可以查看運行目錄下的perfReport.csv文件,標記每次執行次數和時間。

 

作者:costyuan

GitHub地址:https://github.com/bieyuan/.net-core-DTO

 

 

出處:https://www.cnblogs.com/castyuan/p/9324088.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM