之前,在项目中看到过一段通用列表类型转换的代码,直接的实现便是用反射。大概看了下,它用在领域模型转DTO和SOA接口中契约实体的转换等等。首先,它使用了反射,其次,还是在循环中使用,便有了优化的想法。
方法原型如:public static List ConvertList(List source) where TResult : new(),下面贴出代码。说明一下,在此我没有任何的贬义,这段代码可能比较老,其次在项目中,首先是实现功能,如果当时没有更好的实现,就先实现功能,后面有时间可以在优化,毕竟项目有时间节点,个人自己平衡哈。
01.
public
class
ObjectConvertHelperOld
02.
{
03.
/// <summary>
04.
/// 转换单个对象为另外一种类型对象
05.
/// </summary>
06.
/// <typeparam name="'TSource'">待转换的源对象类型</typeparam>
07.
/// <typeparam name="'TResult'">转换的目标对象类型</typeparam>
08.
///<param name="'source'">待转换的源对象
09.
/// <returns>转换的目标对象</returns>
10.
public
static
TResult ConvertObject<tsource, tresult=
""
>(TSource source) where TResult :
new
()
11.
{
12.
TResult result =
new
TResult();
13.
14.
Type sourceType = source.GetType();
15.
Type resultType = result.GetType();
16.
17.
PropertyInfo[] resultProperties = resultType.GetProperties(
18.
BindingFlags.Public | BindingFlags.Instance);
19.
20.
if
(resultProperties !=
null
&& resultProperties.Length >
0
)
21.
{
22.
foreach (PropertyInfo resultProperty in resultProperties)
23.
{
24.
if
(resultProperty.PropertyType.IsGenericType)
25.
{
26.
continue
;
27.
}
28.
29.
PropertyInfo sourceProperty = sourceType.GetProperty(resultProperty.Name);
30.
31.
bool isMatched = sourceProperty !=
null
&&
32.
(!sourceProperty.PropertyType.IsGenericType) &&
33.
(sourceProperty.PropertyType == resultProperty.PropertyType);
34.
35.
if
(isMatched)
36.
{
37.
object currentValue = sourceProperty.GetValue(source,
null
);
38.
resultProperty.SetValue(result, currentValue,
null
);
39.
}
40.
41.
}
42.
}
43.
return
result;
44.
}
45.
46.
/// <summary>
47.
/// 转换列表对象为另外一种列表对象
48.
/// </summary>
49.
/// <typeparam name="'TSource'">待转换的源对象类型</typeparam>
50.
/// <typeparam name="'TResult'">转换的目标对象类型</typeparam>
51.
///<param name="'source'">待转换的源对象
52.
/// <returns>转换的目标对象</returns>
53.
public
static
List<tresult> ConvertList<tsource, tresult=
""
>(List<tsource> source) where TResult :
new
()
54.
{
55.
return
source.ConvertAll<tresult>(ConvertObject<tsource, tresult=
""
>);
56.
}
57.
58.
}</tsource,></tresult></tsource></tsource,></tresult></tsource,>
从上面代码可以看出,它核心是从TSource类型到TResult类型转换,转换中,1、区分大小写,2、以TResult类型中的属性为准,如果源类型中有,就赋值过来(实际上是取两个实体属性的交集),3、还考虑字段是否是泛型等等。。。
如果熟悉Expression Tree的同学,可能就会想到,可以优化反射调用。老赵博客《表达式树与反射调用》系列中有详细实现,推荐大家去看看,绝对干货!我很多这方面的知识从这里学到的,非常感谢啊!
说一下优化思路,其实也不是什么思路了。利用类型字典和LambdaExpression的Compile方法为每组转换的类型缓存一个动态生成的委托。那么委托的调用和直接方法调用性能几乎是一样了。
有时候可能会涉及平台之间的契约转换,比如之前做的一个项目,在.net中调用第三方java的接口,java定义的契约,它的字段命名是camelCasing(小写开头,如:payAmount),我们之间约定是使用http post 数据传输格式采用json字符串,那么json字符串区分大小写,我们两边都使用序列化反序列化等。我这边就需要两份契约了,一份是第三方接口契约实体,采用小写开头命名,第二份是内部契约,采用.net 命名规则PascalCasing,来定义实体属性。这里将内部契约实体转换成第三方契约实体,PayAmount到payAmount的对应转换。
之前考虑的是属性映射区分大小写还是不区分,由调用者参数控制,对于这个需求,简化一下就是属性映射不区分大小写啦,2、以TResult类型中的字段为准(取交集),3、TResult对象的创建是在转换内部创建的,有没有可能这个TResult对象列表已经存在?对于为什么选择属性映射不区分大小写,考虑有二,1、.net中实体中属性的定义,一般不定义重名的(userId,UserId)2、对于TSource中字段和TResult字段完全相同,也不影响啊
优化代码如下:
01.
public
static
class
ObjectConvertHelper
02.
{
03.
private
class
InnerConversion<tsource, tresult=
""
>
04.
{
05.
private
static
readonly Func<tsource, tresult=
""
> s_convert;
06.
static
InnerConversion()
07.
{
08.
s_convert = BuildConvert();
09.
}
10.
private
static
Func<tsource, tresult=
""
> BuildConvert()
11.
{
//(x)=>new TResult{P1=x.p1,P2=x.p2,...};
12.
var paramExp = Expression.Parameter(typeof(TSource),
'x'
);
13.
var sourcePropertyInfos = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance)
14.
.Where(p => p.CanRead && p.CanWrite);
15.
var resultPropertyInfos = typeof(TResult).GetProperties(BindingFlags.Public | BindingFlags.Instance)
16.
.Where(p => p.CanRead && p.CanWrite);
17.
var resultPropertyBindings =
new
List<memberbinding>(resultPropertyInfos.Count());
18.
foreach (var item in resultPropertyInfos)
19.
{
20.
//不区分大小写
21.
PropertyInfo piIgnoreCase = sourcePropertyInfos.Where(x => string.Compare(x.Name, item.Name,
true
) ==
0
).FirstOrDefault();
22.
if
(piIgnoreCase !=
null
)
23.
{
24.
resultPropertyBindings.Add((MemberBinding)Expression.Bind(item, Expression.Property(paramExp, piIgnoreCase))
25.
);
26.
}
27.
}
28.
var body = Expression.MemberInit(
// object initializer
29.
Expression.New(typeof(TResult)),
// ctor
30.
resultPropertyBindings
// property assignments
31.
);
32.
return
Expression.Lambda<func<tsource, tresult=
""
>>(body, paramExp).Compile();
33.
}
34.
/// <summary>
35.
/// 将TSource实体转换到TResult实体(属性匹配规则:1、不区分大小写,2、两个实体属性取交集,3、TResult实体内部创建)
36.
/// </summary>
37.
public
static
Func<tsource, tresult=
""
> Convert
38.
{
39.
get
40.
{
41.
return
s_convert;
42.
}
43.
}
44.
}
45.
46.
/// <summary>
47.
/// 将一种类型列表转换为另一种类型列表
48.
/// </summary>
49.
/// <typeparam name="'TSource'"></typeparam>
50.
/// <typeparam name="'TResult'"></typeparam>
51.
///<param name="'sourceList'">
52.
/// <returns></returns>
53.
public
static
IList<tresult> ConvertList<tsource, tresult=
""
>(IList<tsource> sourceList)
54.
where TSource :
class
55.
where TResult :
class
,
new
()
56.
{
57.
if
(sourceList ==
null
) {
throw
new
ArgumentNullException(
'sourceList'
); }
58.
if
(sourceList.Count ==
0
)
59.
{
60.
return
new
List<tresult>();
61.
}
62.
return
sourceList.Select(p => InnerConversion<tsource, tresult=
""
>.Convert(p)).ToList();
63.
}
64.
65.
public
static
TResult Convert<tsource, tresult=
""
>(TSource source)
66.
where TSource :
class
67.
where TResult :
class
,
new
()
68.
{
69.
if
(source ==
null
) {
throw
new
ArgumentNullException(
'source'
); }
70.
return
InnerConversion<tsource, tresult=
""
>.Convert(source);
71.
}
72.
/// <summary>
73.
/// 浅拷贝实体
74.
/// </summary>
75.
/// <typeparam name="'T'"></typeparam>
76.
///<param name="'source'">
77.
/// <returns></returns>
78.
public
static
T ShallowClone<t>(T source) where T :
class
,
new
()
79.
{
80.
if
(source ==
null
) {
throw
new
ArgumentNullException(
'source'
); }
81.
return
InnerConversion<t, t=
""
>.Convert(source);
82.
}
83.
}</t,></t></tsource,></tsource,></tsource,></tresult></tsource></tsource,></tresult></tsource,></func<tsource,></memberbinding></tsource,></tsource,></tsource,>
类型字典(Type Dictionary):泛型类中的静态字段,会根据泛型的具体类型如InnerConversion有一份对应的静态字段,具体可看装配脑袋文章等。由于系统中的类型个数有限,这样为每种类型缓存一份转换方法,可以说一劳永逸。动态生成委托Func,很强大,可以做很多通用的功能,就像CLR帮我们写代码一样,可参考之前的《Expression Tree实践之通用Parse方法------'让CLR帮我写代码'》等。好了,下面来对比一下两者的性能吧,使用老赵的CodeTimer,测试代码如下:
01.
class
SourceEntity
02.
{
03.
public
int
UserId { get; set; }
04.
public
string name { get; set; }
05.
06.
public
string p3 { get; set; }
07.
public
string p4 { get; set; }
08.
public
string p5 { get; set; }
09.
public
string p6 { get; set; }
10.
public
string p7 { get; set; }
11.
public
string p8 { get; set; }
12.
public
string p9 { get; set; }
13.
public
string p10 { get; set; }
14.
public
string p11 { get; set; }
15.
16.
public
string sourceOther { get; set; }
17.
}
18.
19.
class
ResultEntity
20.
{
21.
public
int
UserId { get; set; }
22.
public
string Name { get; set; }
23.
24.
public
string P3 { get; set; }
25.
public
string P4 { get; set; }
26.
public
string P5 { get; set; }
27.
public
string P6 { get; set; }
28.
public
string P7 { get; set; }
29.
public
string P8 { get; set; }
30.
public
string P9 { get; set; }
31.
public
string P10 { get; set; }
32.
public
string P11 { get; set; }
33.
34.
public
string Comment { get; set; }
35.
}
36.
37.
static
List<sourceentity> GenerateSources(
int
length)
38.
{
39.
List<sourceentity> result =
new
List<sourceentity>();
40.
for
(
int
i =
0
; i < length; i++)
41.
{
42.
result.Add(
new
SourceEntity {
43.
UserId=i,
44.
name=
'stevey'
+i,
45.
p3=i.ToString(),
46.
p4 = i.ToString(),
47.
p5 = i.ToString(),
48.
p6 = i.ToString(),
49.
p7 = i.ToString(),
50.
p8 = i.ToString(),
51.
p9 = i.ToString(),
52.
p10 = i.ToString(),
53.
p11 = i.ToString(),
54.
sourceOther=
'sourceOther'
55.
});
56.
}
57.
return
result;
58.
}
59.
public
static
void
Main(string[] args)
60.
{
61.
List<sourceentity> sourceList = GenerateSources(
100000
);
//生成测试数据
62.
63.
CodeTimer.Initialize();
64.
//对于10W个元素集合执行10次转换,如下
65.
CodeTimer.Time(
'常规反射实现的类型转换'
,
10
, () => {
66.
var resultList = ObjectConvertHelperOld.ConvertList<sourceentity, resultentity=
""
>(sourceList);
67.
});
68.
69.
CodeTimer.Time(
'优化过的类型转换'
,
10
, () => {
70.
var resultList = ObjectConvertHelper.ConvertList<sourceentity, resultentity=
""
>(sourceList);
71.
});
72.
73.
Console.ReadKey();
74.
}</sourceentity,></sourceentity,></sourceentity></sourceentity></sourceentity></sourceentity>
在Release模式下编译后,对于10W个元素的列表执行10次结果如下:
如果执行次数增加,还会有更大的差距,因为已经为类型缓存了委托,就几乎相当于直接方法调用了,而老的实现每次都需要反射SetValue。但是动态编译生成委托,这个过程比较耗时,可以作为初始化,只执行一次,后面就一劳永逸了。
执行100次的结果如下:
好了,就写到这里吧,如有不正之处还请指正,相互交流,共同进步~~