一起谈.NET技术,晚绑定场景下对象属性赋值和取值可以不需要PropertyInfo

简介:   在《一句代码实现批量数据绑定》中,我通过界面控件ID与作为数据源的实体属性名之间的映射实现了批量数据绑定。由于里面频繁涉及对属性的反射——通过反射从实体对象中获取某个属性值;通过反射为控件的某个属性赋值,所以这不是一种高效的操作方式。

  在《一句代码实现批量数据绑定》中,我通过界面控件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之前已经保存起来,并没有频繁去创建)的方式这两者的性能依然有本质的差别。如果你对数字不是敏感,那就看看下面的曲线图吧。

image  五、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();
}
目录
相关文章
|
20天前
|
开发框架 算法 .NET
C#/.NET/.NET Core技术前沿周刊 | 第 15 期(2024年11.25-11.30)
C#/.NET/.NET Core技术前沿周刊 | 第 15 期(2024年11.25-11.30)
|
20天前
|
开发框架 Cloud Native .NET
C#/.NET/.NET Core技术前沿周刊 | 第 16 期(2024年12.01-12.08)
C#/.NET/.NET Core技术前沿周刊 | 第 16 期(2024年12.01-12.08)
|
2月前
|
开发框架 安全 .NET
在数字化时代,.NET 技术凭借跨平台兼容性、丰富的开发工具和框架、高效的性能及强大的安全稳定性,成为软件开发的重要支柱
在数字化时代,.NET 技术凭借跨平台兼容性、丰富的开发工具和框架、高效的性能及强大的安全稳定性,成为软件开发的重要支柱。它不仅加速了应用开发进程,提升了开发质量和可靠性,还促进了创新和业务发展,培养了专业人才和技术社区,为软件开发和数字化转型做出了重要贡献。
45 5
|
2月前
|
传感器 人工智能 供应链
.NET开发技术在数字化时代的创新作用,从高效的开发环境、强大的性能表现、丰富的库和框架资源等方面揭示了其关键优势。
本文深入探讨了.NET开发技术在数字化时代的创新作用,从高效的开发环境、强大的性能表现、丰富的库和框架资源等方面揭示了其关键优势。通过企业级应用、Web应用及移动应用的创新案例,展示了.NET在各领域的广泛应用和巨大潜力。展望未来,.NET将与新兴技术深度融合,拓展跨平台开发,推动云原生应用发展,持续创新。
51 4
|
2月前
|
机器学习/深度学习 人工智能 Cloud Native
在数字化时代,.NET 技术凭借其跨平台兼容性、丰富的类库和工具集以及卓越的性能与效率,成为软件开发的重要平台
在数字化时代,.NET 技术凭借其跨平台兼容性、丰富的类库和工具集以及卓越的性能与效率,成为软件开发的重要平台。本文深入解析 .NET 的核心优势,探讨其在企业级应用、Web 开发及移动应用等领域的应用案例,并展望未来在人工智能、云原生等方面的发展趋势。
49 3
|
2月前
|
敏捷开发 缓存 中间件
.NET技术的高效开发模式,涵盖面向对象编程、良好架构设计及高效代码编写与管理三大关键要素
本文深入探讨了.NET技术的高效开发模式,涵盖面向对象编程、良好架构设计及高效代码编写与管理三大关键要素,并通过企业级应用和Web应用开发的实践案例,展示了如何在实际项目中应用这些模式,旨在为开发者提供有益的参考和指导。
47 3
|
2月前
|
开发框架 安全 Java
.NET技术的独特魅力与优势,涵盖高效的开发体验、强大的性能表现、高度的可扩展性及丰富的生态系统等方面,展示了其在软件开发领域的核心竞争力
本文深入探讨了.NET技术的独特魅力与优势,涵盖高效的开发体验、强大的性能表现、高度的可扩展性及丰富的生态系统等方面,展示了其在软件开发领域的核心竞争力。.NET不仅支持跨平台开发,具备出色的安全性和稳定性,还能与多种技术无缝集成,为企业级应用提供全面支持。
43 3
|
XML 数据格式 API
1分钟生成Net对象的注释
我们在开发过程中,肯定会有几个项目作为基础项目,存放一些比较常用的类和方法,供其他项目使用.一般来说,方法实现以后,就不想再去管它了,以致于新加入的某个伙计问这个项目里的方法有没注释或说明啊,一般的答案都是木有.
888 0
|
20天前
|
监控 前端开发 API
一款基于 .NET MVC 框架开发、功能全面的MES系统
一款基于 .NET MVC 框架开发、功能全面的MES系统
|
4月前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
55 7