来源: C# 高性能对象映射(表达式树实现) – coder拾遗 – 博客园
前言
上篇简单实现了对象映射,针对数组,集合,嵌套类并没有给出实现,这一篇继续完善细节。
开源对象映射类库映射分析
1.AutoMapper
实现原理:主要通过表达式树Api 实现对象映射
优点: .net功能最全的对象映射类库。
缺点:当出现复杂类型和嵌套类型时性能直线下降,甚至不如序列化快
2.TinyMapper
实现原理:主要通过Emit 实现对象映射
优点:速度非常快。在处理复杂类型和嵌套类型性能也很好
缺点:相对AutoMapper功能上少一些,Emit的实现方案,在代码阅读和调试上相对比较麻烦,而表达式树直接观察 DebugView中生成的代码结构便可知道问题所在
3. 本文的对象映射库
针对AutoMapper 处理复杂类型和嵌套类型时性能非常差的情况,自己实现一个表达式树版的高性能方案
此篇记录下实现对象映射库的过程
构造测试类
1 public class TestA
2 {
3 public int Id { get; set; }
4 public string Name { get; set; }
5
6 public TestC TestClass { get; set; }
7
8 public IEnumerable<TestC> TestLists { get; set; }
9 }
10
11 public class TestB
12 {
13 public int Id { get; set; }
14 public string Name { get; set; }
15
16 public TestD TestClass { get; set; }
17
18 public TestD[] TestLists { get; set; }
19 }
20
21 public class TestC
22 {
23 public int Id { get; set; }
24 public string Name { get; set; }
25
26 public TestC SelfClass { get; set; }
27 }
28
29 public class TestD
30 {
31 public int Id { get; set; }
32 public string Name { get; set; }
33
34 public TestD SelfClass { get; set; }
35 }
1.初步实现
利用表达式树给属性赋值 利用 Expresstion.New构造 var b=new B{};
1 private static Func<TSource, TTarget> GetMap<TSource, TTarget>()
2 {
3 var sourceType = typeof(TSource);
4 var targetType = typeof(TTarget);
5
6 //构造 p=>
7 var parameterExpression = Expression.Parameter(sourceType, "p");
8
9 //构造 p=>new TTarget{ Id=p.Id,Name=p.Name };
10 var memberBindingList = new List<MemberBinding>();
11 foreach (var sourceItem in sourceType.GetProperties())
12 {
13 var targetItem = targetType.GetProperty(sourceItem.Name);
14 if (targetItem == null || sourceItem.PropertyType != targetItem.PropertyType)
15 continue;
16
17 var property = Expression.Property(parameterExpression, sourceItem);
18 var memberBinding = Expression.Bind(targetItem, property);
19 memberBindingList.Add(memberBinding);
20 }
21 var memberInitExpression = Expression.MemberInit(Expression.New(targetType), memberBindingList);
22
23 var lambda = Expression.Lambda<Func<TSource, TTarget>>(memberInitExpression, parameterExpression );
24
25 Console.WriteLine(lambda);
26 return lambda.Compile();
27 }
调用如下
14
15 class Program
16 {
17 static void Main(string[] args)
18 {
19 var testA = new TestA { Id = 1, Name = "张三" };
20 var func = Map<TestA, TestB>();
21 TestB testB = func(testA);
22 Console.WriteLine($"testB.Id={testB.Id},testB.Name={testB.Name}");
23 Console.ReadLine();
24 }
25 }
输出结果

总结:此方法需要调用前需要手动编译下,然后再调用委托没有缓存委托,相对麻烦。
2.缓存实现
利用静态泛型类缓存泛型委托
1 public class DataMapper<TSource, TTarget>
2 {
3 private static Func<TSource, TTarget> MapFunc { get; set; }
4
5 public static TTarget Map(TSource source)
6 {
7 if (MapFunc == null)
8 MapFunc = GetMap();//方法在上边
9 return MapFunc(source);
10 }11 }
调用方法
1 static void Main(string[] args)
2 {
3 var testA = new TestA { Id = 1, Name = "张三" };
4 TestB testB = DataMapper<TestA, TestB>.Map(testA);//委托不存在时自动生成,存在时调用静态缓存
5
6 Console.WriteLine($"testB.Id={testB.Id},testB.Name={testB.Name}");
7 Console.ReadLine();
8 }
输出结果

总结:引入静态泛型类能解决泛型委托缓存提高性能,但是有两个问题 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 }
将上面的代码翻译成表达式树
1 private static Func<TSource, TTarget> GetMap()
2 {
3 var sourceType = typeof(TSource);
4 var targetType = typeof(TTarget);
5
6 //Func委托传入变量
7 var parameter = Expression.Parameter(sourceType);
8
9 //声明一个返回值变量
10 var variable = Expression.Variable(targetType);
11 //创建一个if条件表达式
12 var test = Expression.NotEqual(parameter, Expression.Constant(null, sourceType));// p==null;
13 var ifTrue = Expression.Block(GetExpression(parameter, variable, sourceType, targetType));
14 var IfThen = Expression.IfThen(test, ifTrue);
15
16 //构造代码块
17 var block = Expression.Block(new[] { variable }, parameter, IfThen, variable);
18
19 var lambda = Expression.Lambda<Func<TSource, TTarget>>(block, parameter);
20 return lambda.Compile();
21 }
22
23 private static List<Expression> GetExpression(Expression parameter, Expression variable, Type sourceType, Type targetType)
24 {
25 //创建一个表达式集合
26 var expressions = new List<Expression>();
27
28 expressions.Add(Expression.Assign(variable, Expression.MemberInit(Expression.New(targetType))));
29
30 foreach (var targetItem in targetType.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite))
31 {
32 var sourceItem = sourceType.GetProperty(targetItem.Name);
33
34 //判断实体的读写权限
35 if (sourceItem == null || !sourceItem.CanRead || sourceItem.PropertyType.IsNotPublic)
36 continue;
37
38 var sourceProperty = Expression.Property(parameter, sourceItem);
39 var targetProperty = Expression.Property(variable, targetItem);
40
41 //判断都是class 且类型不相同时
42 if (targetItem.PropertyType.IsClass && sourceItem.PropertyType.IsClass && targetItem.PropertyType != sourceItem.PropertyType)
43 {
44 if (targetItem.PropertyType != targetType)//不处理嵌套循环的情况
45 {
46 //由于类型是class 所以默认值是null
47 var testItem = Expression.NotEqual(sourceProperty, Expression.Constant(null, sourceItem.PropertyType));
48
49 var itemExpressions = GetExpression(sourceProperty, targetProperty, sourceItem.PropertyType, targetItem.PropertyType);
50 var ifTrueItem = Expression.Block(itemExpressions);
51
52 var IfThenItem = Expression.IfThen(testItem, ifTrueItem);
53 expressions.Add(IfThenItem);
54
55 continue;
56 }
57 }
58
59 //目标值类型时 且两者类型不一致时跳过
60 if (targetItem.PropertyType != sourceItem.PropertyType)
61 continue;
62
63 expressions.Add(Expression.Assign(targetProperty, sourceProperty));
64 }
65
66 return expressions;
67 }
总结:此方案,运用 Expression.IfThen(testItem, ifTrueItem) 判断空值问题,通过递归调用 GetExpression()方法,处理复杂类型。 但是。。。针对嵌套类仍然不能解决。因为表达式树是在实际调用方法之前就生成的,在没有实际的
参数值传入之前,生成的表达式是不知道有多少层级的。有个比较low的方案是,预先设定嵌套层级为10层,然后生成一个有10层 if(P!=null) 的判断。如果传入的参数层级超过10层了呢,就得手动调整生成的树,此方案也否决。
最后得出的结论只能在表达式中动态调用方法。
4.最终版本
通过动态调用方法解决嵌套类,代码如下
using static System.Linq.Expressions.Expression;
public static class Mapper<TSource, TTarget> where TSource : class where TTarget : class
{
public readonly static Func<TSource, TTarget> MapFunc = GetMapFunc();
public 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);
public static List<TTarget> MapList(IEnumerable<TSource> sources)=> sources.Select(MapFunc).ToList();
/// <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 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();
}
/// <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));
//构造回调 Mapper<TSource, TTarget>.Map()
var mapperType = typeof(Mapper<,>).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));
//构造回调 Mapper<TSource, TTarget>.MapList()
var sourceArg = sourceType.IsArray ? sourceType.GetElementType() : sourceType.GetGenericArguments()[0];
var targetArg = targetType.IsArray ? targetType.GetElementType() : targetType.GetGenericArguments()[0];
var mapperType = typeof(Mapper<,>).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;
}
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();
}
}
输出的 表达式
格式化后
1 p => IIF((p != null),
2 new TestB()
3 {
4 Id = p.Id,
5 Name = p.Name,
6 TestClass = IIF(
7 (p.TestClass != null),
8 Map(p.TestClass),
9 null
10 ),
11 TestLists = IIF(
12 (p.TestLists != null),
13 MapList(p.TestLists).ToArray(),
14 null
15 )
16 },
17 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.测试类
1 public static class MapperTest
2 {
3 //执行次数
4 public static int Count = 100000;
5
6 //简单类型
7 public static void Nomal()
8 {
9 Console.WriteLine($"******************简单类型:{Count / 10000}万次执行时间*****************");
10 var model = new TestA
11 {
12 Id =1,
13 Name = "张三",
14 };
15
16 //计时
17 var sw = Stopwatch.StartNew();
18 for (int i = 0; i < Count; i++)
19 {
20 if (model != null)
21 {
22 var b = new TestB
23 {
24 Id = model.Id,
25 Name = model.Name,
26 };
27 }
28 }
29 sw.Stop();
30 Console.WriteLine($"原生的时间:{sw.ElapsedMilliseconds}ms");
31
32 Exec(model);
33 }
34
35 //复杂类型
36 public static void Complex()
37 {
38 Console.WriteLine($"********************复杂类型:{Count / 10000}万次执行时间*********************");
39 var model = new TestA
40 {
41 Id = 1,
42 Name = "张三",
43 TestClass = new TestC
44 {
45 Id = 2,
46 Name = "lisi",
47 },
48 };
49
50 //计时
51 var sw = Stopwatch.StartNew();
52 for (int i = 0; i < Count; i++)
53 {
54
55 if (model != null)
56 {
57 var b = new TestB
58 {
59 Id = model.Id,
60 Name = model.Name,
61 };
62 if (model.TestClass != null)
63 {
64 b.TestClass = new TestD
65 {
66 Id = i,
67 Name = "lisi",
68 };
69 }
70 }
71 }
72 sw.Stop();
73 Console.WriteLine($"原生的时间:{sw.ElapsedMilliseconds}ms");
74 Exec(model);
75 }
76
77 //嵌套类型
78 public static void Nest()
79 {
80 Console.WriteLine($"*****************嵌套类型:{Count / 10000}万次执行时间*************************");
81 var model = new TestA
82 {
83 Id = 1,
84 Name = "张三",
85 TestClass = new TestC
86 {
87 Id = 1,
88 Name = "lisi",
89 SelfClass = new TestC
90 {
91 Id = 2,
92 Name = "lisi",
93 SelfClass = new TestC
94 {
95 Id = 3,
96 Name = "lisi",
97 SelfClass = new TestC
98 {
99 Id = 4,
100 Name = "lisi",
101 },
102 },
103 },
104 },
105 };
106 //计时
107 var item = model;
108 var sw = Stopwatch.StartNew();
109 for (int i = 0; i < Count; i++)
110 {
111 //这里每一步需要做非空判断的,书写太麻烦省去了
112 if (model != null)
113 {
114 var b = new TestB
115 {
116 Id = model.Id,
117 Name = model.Name,
118 TestClass = new TestD
119 {
120 Id = model.TestClass.Id,
121 Name = model.TestClass.Name,
122 SelfClass = new TestD
123 {
124 Id = model.TestClass.SelfClass.Id,
125 Name = model.TestClass.SelfClass.Name,
126 SelfClass = new TestD
127 {
128 Id = model.TestClass.SelfClass.SelfClass.Id,
129 Name = model.TestClass.SelfClass.SelfClass.Name,
130 SelfClass = new TestD
131 {
132 Id = model.TestClass.SelfClass.SelfClass.SelfClass.Id,
133 Name = model.TestClass.SelfClass.SelfClass.SelfClass.Name,
134 },
135 },
136 },
137 },
138 };
139 }
140 }
141 sw.Stop();
142 Console.WriteLine($"原生的时间:{sw.ElapsedMilliseconds}ms");
143
144 Exec(model);
145 }
146
147 //集合
148 public static void List()
149 {
150 Console.WriteLine($"********************集合类型:{Count/10000}万次执行时间***************************");
151
152 var model = new TestA
153 {
154 Id = 1,
155 Name = "张三",
156 TestLists = new List<TestC> {
157 new TestC{
158 Id = 1,
159 Name = "张三",
160 },
161 new TestC{
162 Id = -1,
163 Name = "张三",
164 },
165 }
166 };
167
168
169 //计时
170 var sw = Stopwatch.StartNew();
171 for (int i = 0; i < Count; i++)
172 {
173 var item = model;
174 if (item != null)
175 {
176 var b = new TestB
177 {
178 Id = item.Id,
179 Name = item.Name,
180 TestLists = new List<TestD> {
181 new TestD{
182 Id = item.Id,
183 Name = item.Name,
184 },
185 new TestD{
186 Id = -item.Id,
187 Name = item.Name,
188 },
189 }.ToArray()
190 };
191 }
192 }
193 sw.Stop();
194 Console.WriteLine($"原生的时间:{sw.ElapsedMilliseconds}ms");
195
196 Exec(model);
197 }
198
199 public static void Exec(TestA model)
200 {
201 //表达式
202 Mapper<TestA, TestB>.Map(model);
203 var sw = Stopwatch.StartNew();
204 for (int i = 0; i < Count; i++)
205 {
206 var b = Mapper<TestA, TestB>.Map(model);
207 }
208 sw.Stop();
209 Console.WriteLine($"表达式的时间:{sw.ElapsedMilliseconds}ms");
210
211 //AutoMapper
212 sw.Restart();
213 for (int i = 0; i < Count; i++)
214 {
215 var b = AutoMapper.Mapper.Map<TestA, TestB>(model);
216 }
217 sw.Stop();
218 Console.WriteLine($"AutoMapper时间:{sw.ElapsedMilliseconds}ms");
219
220 //TinyMapper
221 sw.Restart();
222 for (int i = 0; i < Count; i++)
223 {
224 var b = TinyMapper.Map<TestA, TestB>(model);
225 }
226 sw.Stop();
227 Console.WriteLine($"TinyMapper时间:{sw.ElapsedMilliseconds}ms");
228 }
229 }
230
231
2.调用测试
1 static void Main(string[] args)
2 {
3 AutoMapper.Mapper.Initialize(cfg => cfg.CreateMap<TestA, TestB>());
4 TinyMapper.Bind<TestA, TestB>();
5 Mapper<TestA, TestB>.Map(new TestA());
6
7
8 MapperTest.Count = 10000;
9 MapperTest.Nomal();
10 MapperTest.Complex();
11 MapperTest.Nest();
12 MapperTest.List();
13
14 MapperTest.Count = 100000;
15 MapperTest.Nomal();
16 MapperTest.Complex();
17 MapperTest.Nest();
18 MapperTest.List();
19
20 MapperTest.Count = 1000000;
21 MapperTest.Nomal();
22 MapperTest.Complex();
23 MapperTest.Nest();
24 MapperTest.List();
25
26 MapperTest.Count = 10000000;
27 MapperTest.Nomal();
28 MapperTest.Complex();
29 MapperTest.Nest();
30 MapperTest.List();
31
32
33 Console.WriteLine($"------------结束--------------------");
34 Console.ReadLine();
35 }
3.结果
1万次

10万次

100万次

1000万次

上图结果AutoMapper 在非简单类型的转换上比其他方案有50倍以上的差距,几乎就跟反射的结果一样。
作者:costyuan
GitHub地址:https://github.com/bieyuan/.net-core-DTO
地址:http://www.cnblogs.com/castyuan/p/9324088.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
如果文中有什么错误,欢迎指出,谢谢!
Mikel
