博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一起谈.NET技术,晚绑定场景下对象属性赋值和取值可以不需要PropertyInfo
阅读量:6409 次
发布时间:2019-06-23

本文共 9270 字,大约阅读时间需要 30 分钟。

  在《》中,我通过界面控件ID与作为数据源的实体属性名之间的映射实现了批量数据绑定。由于里面频繁涉及对属性的反射——通过反射从实体对象中获取某个属性值;通过反射为控件的某个属性赋值,所以这不是一种高效的操作方式。为了提升性能,我通过IL Emit的方式创建了一个PropertyAccessor组件,以实现高效的属性操作。如果你看了我在文中给出的三种属性操作性能的测试结果,相信会对PropertyAccessor的作用有深刻的印象。[源代码从下载]

目录:

一、PropertyAccessor与PropertyAccessor<T>的API定义
二、如何通过PropertyAccessor获取属性值和为属性赋值
三、Set和Get的实现
四、比较三种属性操作的性能
五、PropertyAccessor的ExpressionTree版本

  一、PropertyAccessor与PropertyAccessor<T>的API定义

  我们照例从编程——即如何使用PropertyAccessor进行属性操作(获取属性值/为属性赋值)讲起,所有先来看看PropertyAccessor提供了哪些API功我们调用。从下面的代码片断我们可以看到,PropertyAccessor得构造函数接受两个参数:目标对象的类型和属性名称,然后通过Get获取目标对象相应属性的值,通过Set方法为目标对象的属性进行赋值。此外,PropertyAccessor还提供了两个对应的Get/Set静态方法通过指定具体的目标对象和属性名称实现相同的操作。

 
public
class
PropertyAccessor
{
public
PropertyAccessor(Type targetType,
string
propertyName);
public
object
Get(
object
obj);
public
void
Set(
object
obj,
object
value);
public
static
object
Get(
object
obj,
string
propertyName);
public
static
void
Set(
object
obj,
string
propertyName,
object
value);
//
Others...
}

  如果预先知道了目标对象的类型,可能使用泛型的PropertyAccessor<T>会使操作更加方便。PropertyAccessor<T>继承自PropertyAccessor,定义如下:

 
public
class
PropertyAccessor
<
T
>
: PropertyAccessor
{
public
PropertyAccessor(
string
propertyName);
public
static
object
Get(T obj,
string
propertyName);
public
static
void
Set(T obj,
string
propertyName,
object
value);
}

  二、如何通过PropertyAccessor获取属性值和为属性赋值

  现在我们来演示如何通PropertyAccessor<T>来对目标对象的属性赋值,以及如何或者目标对象相应属性的值。现在我们定义如下一个实体类型:Contact。

 
public
class
Contact
{
public
string
FirstName {
get
;
set
; }
public
string
LastName {
get
;
set
; }
public
string
Gender {
get
;
set
; }
public
int
?
Age {
get
;
set
; }
public
DateTime
?
Birthday {
get
;
set
; }
}

  然后我们在一个Console应用的Main方法中编写如下一段代码。在这段代码中,我创建了一个Contact对象,然后通过调用PropertyAccessor<Contact>类型的静态方法Set为该对象的各个属性进行复制。然后将各个属性值按照一定的格式打印出来,而获取属性值是通过调用静态方法Get完成的。

 
static
void
Main(
string
[] args)
{
var contact
=
new
Contact();
PropertyAccessor
<
Contact
>
.Set(contact,
"
FirstName
"
,
"
Jiang
"
);
PropertyAccessor
<
Contact
>
.Set(contact,
"
LastName
"
,
"
Jin Nan
"
);
PropertyAccessor
<
Contact
>
.Set(contact,
"
Gender
"
,
"
Male
"
);
PropertyAccessor
<
Contact
>
.Set(contact,
"
Age
"
,
30
);
PropertyAccessor
<
Contact
>
.Set(contact,
"
Birthday
"
,
new
DateTime(
1981
,
8
,
24
));
Console.WriteLine(
"
Contact({0} {1})\n\tGender\t:{2}\n\tAge\t:{3}\n\tBirth\t:{4}
"
,
PropertyAccessor
<
Contact
>
.Get(contact,
"
FirstName
"
),
PropertyAccessor
<
Contact
>
.Get(contact,
"
LastName
"
),
PropertyAccessor
<
Contact
>
.Get(contact,
"
Gender
"
),
PropertyAccessor
<
Contact
>
.Get(contact,
"
Age
"
),
PropertyAccessor
<
Contact
>
.Get(contact,
"
Birthday
"
));
}

  输出结果:

 
Contact(Jiang Jin Nan)
Gender :Male
Age :
30
Birth :
8
/
24
/
1981
12
:
00
:
00
AM

  三、Set和Get的实现

  虽然PropertyAccessor是一个很小的组件,但也不太可能将所有的代码列出来。在这里,我只是只能将核心部分作一下简单介绍,如果你想了解整个PropertyAccessor的实现,可以下载源代码。PropertyAccessor的两个核心的方法就是Get和Set。而在内部,它们对应着两个核心的方法:CreateGetFunction和CreateSetAction,它们利用IL Emit。下面是CreateGetFunction的实现:创建一个DynamicMethod对象,通过IL Emit调用属性的Getter方法,并将结果返回。最后通过DynamicMethod的CreateDelegate方法创建一个Func<object,object>委托对象并在本地缓存起来,供或许的获取属性值操作之用。

 
private
Func
<
object
,
object
>
CreateGetFunction()
{
//
...
DynamicMethod method
=
new
DynamicMethod(
"
GetValue
"
,
typeof
(
object
),
new
Type[] {
typeof
(
object
) });
ILGenerator ilGenerator
=
method.GetILGenerator();
ilGenerator.DeclareLocal(
typeof
(
object
));
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Castclass,
this
.TargetType);
ilGenerator.EmitCall(OpCodes.Call,
this
.GetMethod,
null
);
if
(
this
.GetMethod.ReturnType.IsValueType)
{
ilGenerator.Emit(OpCodes.Box,
this
.GetMethod.ReturnType);
}
ilGenerator.Emit(OpCodes.Stloc_0);
ilGenerator.Emit(OpCodes.Ldloc_0);
ilGenerator.Emit(OpCodes.Ret);
method.DefineParameter(
1
, ParameterAttributes.In,
"
value
"
);
return
(Func
<
object
,
object
>
)method.CreateDelegate(
typeof
(Func
<
object
,
object
>
));
}

  与CreateGetFunction类似,CreateSetAction同样创建一个DynamicMethod对象,通过IL Emit的方式调用属性的Setter方法。最后通过DynamicMethod的CreateDelegate方法创建一个Action<object,object>委托对象并在本地缓存起来,供后续的属性赋值操作之用。

 
private
Action
<
object
,
object
>
CreateSetAction()
{
//
...
DynamicMethod method
=
new
DynamicMethod(
"
SetValue
"
,
null
,
new
Type[] {
typeof
(
object
),
typeof
(
object
) });
ILGenerator ilGenerator
=
method.GetILGenerator();
Type paramType
=
this
.SetMethod.GetParameters()[
0
].ParameterType;
ilGenerator.DeclareLocal(paramType);
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Castclass,
this
.TargetType);
ilGenerator.Emit(OpCodes.Ldarg_1);
if
(paramType.IsValueType)
{
ilGenerator.Emit(OpCodes.Unbox, paramType);
if
(valueTpyeOpCodes.ContainsKey(paramType))
{
OpCode load
=
(OpCode)valueTpyeOpCodes[paramType];
ilGenerator.Emit(load);
}
else
{
ilGenerator.Emit(OpCodes.Ldobj, paramType);
}
}
else
{
ilGenerator.Emit(OpCodes.Castclass, paramType);
}
ilGenerator.EmitCall(OpCodes.Callvirt,
this
.SetMethod,
null
);
ilGenerator.Emit(OpCodes.Ret);
method.DefineParameter(
1
, ParameterAttributes.In,
"
obj
"
);
method.DefineParameter(
2
, ParameterAttributes.In,
"
value
"
);
return
(Action
<
object
,
object
>
)method.CreateDelegate(
typeof
(Action
<
object
,
object
>
));
}

  四、比较三种属性操作的性能

  我想大家最关心的还是“性能”的问题,现在我们就来编写一个性能测试的程序。在这个程序中我们比较三种典型的属性操作耗费的时间:直接通过属性赋值(或者取值)、通过IL Emit(即PropertyAccessor)和PropertyInfo对属性赋值(或者取值)。我们定义两个简单的类型Foo和Bar,Foo中定义一个类型和名称为Bar的可读写的属性。

 
public
class
Foo
{
public
Bar Bar {
get
;
set
; }
}
public
class
Bar
{ }

  下面是用于比较三种属性复制操作的测试程序SetTest,方法参数为复制操作的次数,最后将三种属性赋值操作的总时间(单位毫秒)分别打印出来。

 
public
static
void
SetTest(
int
times)
{
Foo foo
=
new
Foo();
Bar bar
=
new
Bar();
Stopwatch stopwatch
=
new
Stopwatch();
PropertyAccessor
<
Foo
>
propertyAccessor
=
new
PropertyAccessor
<
Foo
>
(
"
Bar
"
);
PropertyInfo propertyInfo
=
typeof
(Foo).GetProperty(
"
Bar
"
);
stopwatch.Start();
for
(
int
i
=
0
; i
<
times; i
++
)
{
foo.Bar
=
bar;
}
long
duration1
=
stopwatch.ElapsedMilliseconds;
stopwatch.Restart();
for
(
int
i
=
0
; i
<
times; i
++
)
{
propertyAccessor.Set(foo, bar);
}
long
duration2
=
stopwatch.ElapsedMilliseconds;
stopwatch.Restart();
for
(
int
i
=
0
; i
<
times; i
++
)
{
propertyInfo.SetValue(foo, bar,
null
);
}
long
duration3
=
stopwatch.ElapsedMilliseconds;
Console.WriteLine(
"
{0,-10}{1,-10}{2,-10}{3,-10}
"
, times, duration1, duration2, duration3);
}

  下面是下面是用于比较三种或者属性值操作的测试程序GetTest,定义形式和上面一样:

 
public
static
void
GetTest(
int
times)
{
Foo foo
=
new
Foo { Bar
=
new
Bar() };
Stopwatch stopwatch
=
new
Stopwatch();
PropertyAccessor
<
Foo
>
propertyAccessor
=
new
PropertyAccessor
<
Foo
>
(
"
Bar
"
);
PropertyInfo propertyInfo
=
typeof
(Foo).GetProperty(
"
Bar
"
);
stopwatch.Start();
for
(
int
i
=
0
; i
<
times; i
++
)
{
var bar
=
foo.Bar;
}
long
duration1
=
stopwatch.ElapsedMilliseconds;
stopwatch.Restart();
for
(
int
i
=
0
; i
<
times; i
++
)
{
var bar
=
propertyAccessor.Get(foo);
}
long
duration2
=
stopwatch.ElapsedMilliseconds;
stopwatch.Restart();
for
(
int
i
=
0
; i
<
times; i
++
)
{
var bar
=
propertyInfo.GetValue(foo,
null
);
}
long
duration3
=
stopwatch.ElapsedMilliseconds;
Console.WriteLine(
"
{0,-10}{1,-10}{2,-10}{3,-10}
"
, times, duration1, duration2, duration3);
}

  然后,我们在Console应用的Main方法中编写如下的代码,旨在测试次数分别为100000(十万)、1000000(一百万)和10000000(一千万)下三种不同形式的属性操作所耗用的时间。

 
static
void
Main(
string
[] args)
{
Console.WriteLine(
"
{0,-10}{1,-10}{2,-10}{3,-10}
"
,
"
Times
"
,
"
General
"
,
"
IL Emit
"
,
"
Reflection
"
);
SetTest(
100000
);
SetTest(
1000000
);
SetTest(
10000000
);
Console.WriteLine();
GetTest(
100000
);
GetTest(
1000000
);
GetTest(
10000000
);
}

  输出结果:

 
Times General IL Emit Reflection
100000
1
17
204
1000000
12
110
1918
10000000
131
1103
18919
100000
1
10
153
1000000
11
101
1534
10000000
112
1009
15425

  由于我的笔记本已经差不多5年的历史,性能不是很好,所以更能反映出三种操作类型的性能差异。我们对属性直接进行赋值和取值是最快的,这一点没有什么好说的。我们关心的是,IL Emit的方式和单纯使用PropertyInfo进行反射(并且值得一提的是:PropertyInfo之前已经保存起来,并没有频繁去创建)的方式这两者的性能依然有本质的差别。如果你对数字不是敏感,那就看看下面的曲线图吧。

  五、PropertyAccessor的ExpressionTree版本(2011-03-25)

  对于很多人来说,IL Emit编程是一件很繁琐的事。反正我多这比较头疼,我一般的做法都是将需要的逻辑通过代码写出来,编译之后跟据IL写Emit代码。而我们更喜欢采用的则是ExpressionTree,为此我编写了PropertyAccessor的ExpressionTree版本(你可以从下载)。两个版本主要的不同还是在于上述两个方法:CreateGetFunction和CreateSetAction。下面是两个方法的定义:

 
private
Func
<
object
,
object
>
CreateGetFunction()
{
var getMethod
=
this
.Property.GetGetMethod();
var target
=
Expression.Parameter(
typeof
(
object
),
"
target
"
);
var castedTarget
=
getMethod.IsStatic
?
null
: Expression.Convert(target,
this
.TargetType);
var getProperty
=
Expression.Property(castedTarget,
this
.Property);
var castPropertyValue
=
Expression.Convert(getProperty,
typeof
(
object
));
return
Expression.Lambda
<
Func
<
object
,
object
>>
(castPropertyValue, target).Compile();
}
private
Action
<
object
,
object
>
CreateSetAction()
{
var setMethod
=
this
.Property.GetSetMethod();
var target
=
Expression.Parameter(
typeof
(
object
),
"
target
"
);
var propertyValue
=
Expression.Parameter(
typeof
(
object
),
"
value
"
);
var castedTarget
=
setMethod.IsStatic
?
null
: Expression.Convert(target,
this
.TargetType);
var castedpropertyValue
=
Expression.Convert(propertyValue,
this
.PropertyType);
var propertySet
=
Expression.Call(castedTarget, setMethod, castedpropertyValue);
return
Expression.Lambda
<
Action
<
object
,
object
>>
(propertySet, target, propertyValue).Compile();
}

转载于:https://www.cnblogs.com/waw/archive/2011/09/01/2162905.html

你可能感兴趣的文章
Fastjson 的 Set类型和 WriteClassName 选项引起的BUG
查看>>
翻译: 星球生成 II
查看>>
IOS 多线程
查看>>
python序列化数据本地存放
查看>>
#CCNA#IP地址、子网划分参考资料网址
查看>>
比较不错的图片上传插件
查看>>
判偶不判奇
查看>>
Sequelize 数据库的支持
查看>>
BigDecimal类的加减乘除
查看>>
node.js发送邮件email
查看>>
查看nginx配置文件路径的方法
查看>>
接口性能调优方案探索
查看>>
kali安装包或更新时提示“E: Sub-process /usr/bin/dpkg return”
查看>>
网站管理后台模板 Charisma
查看>>
EL:empty的用法
查看>>
Saltstack配置之 nodegroups
查看>>
Servlet和JSP优化经验总结
查看>>
squid使用rotate轮询(分割)日志
查看>>
VS2015安装EF Power Tools
查看>>
MySQL主从复制(笔记)
查看>>