晚绑定场景下对象属性赋值和取值可以不需要Pro“.NET技术”pertyInfo

  在《一句代码实现批量数据绑定》中,我通过界面控件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上海企业网站制作pan style="color: #000000;"> 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, 上海网站建设an style="color: #0000ff;">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++上海企业网站设计与制作yle="color: #000000;">)
{
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();
}

时间: 2024-09-26 17:40:48

晚绑定场景下对象属性赋值和取值可以不需要Pro“.NET技术”pertyInfo的相关文章

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

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

c#程序问题-c#编程对象属性赋值问题!

问题描述 c#编程对象属性赋值问题! label6.Text = "A"; label7.Text = "B"; label8.Text = "C"; label9.Text = "D"; label10.Text = "E"; label11.Text = "A"; label12.Text = "B"; label13.Text = "C";

js实现hashtable的赋值、取值、遍历的教程

本文实例讲述了js实现hashtable的赋值.取值.遍历操作.分享给大家供大家参考,具体如下: 哈希表(Hashtable)这个概率应该是#c里面的概念,用来赋值.取值.遍历.排序操作提高效率.想起这个东西其实使我们以前经常遇到这样的面试题,一个很大的数组可能有100000个,如何快速知道它里面的出现最多的次数,那么这里我们可能就要用Hashtable的相关知识了.Javascript中,object的实现就是hash表,因此只要在object上封装点方法,再利用原生的hasOwnProper

实用ExtJS教程100例-011:ExtJS Form 使用JSON数据赋值和取值

上一节中我们演示了ExtJS Form的异步加载和提交数据,本节中我们将演示如何使用JSON数据为ExtJS Form中的字段赋值和取值. 系列ExtJS教程持续更新中,点击查看>>最新ExtJS教程目录 使用JSON数据为字段赋值 var formCmp = this.up("form"); var form = formCmp.getForm(); var userValues = { UserName: "Qi Fei", Email: "

数据库组件 Hxj.Data (三十)(Asp.Net 页面自动赋值与取值)

在项目中的页面中经常会遇到界面的赋值与取值,特别是页面中项特别多的时候,取值和赋值往往是 体力活. 在组件中添加了一个简单的辅助,取值通过Request.From[""]来实现,赋值则 document.getElementById("").value来实现. 下面我们来看示例代码: 例子是:Northwind数据库的Products表 页面代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transi

SelecT下拉框选中和取值的解决方法_jquery

 Select下拉框的问题,想在选择一个选项后,前台显示做出变动,并且知道选择的是第几个选项. 这个很好解决: 如下: <div class="page-header"> <div class="form-horizontal"> <div class="control-label col-lg-0"> </div> <div class="col-lg-2"> &l

Servlet cookie 赋值与取值及页面之前传值处理方法

Servlet cookie 赋值与取值及页面之前传值处理方法,下面先是讲一下关于如何获取与给cookies设置值,然后再利用一个实例详细值了servlet cookie实例操作. import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.Http

CYQ.Data 快速开发之UI(赋值、取值、绑定)原理

昨夜园子猴子问了几个我CYQ.Data使用的小问题,经过简单解答后,他表示"妈妈再也不用担心我的学习",并于事后以资鼓励,希望这框架越走越好. 除了技术上的交流,双方在生活,S上面的问题上也进行了双边友好交流,最后猴子给发了一个国外的Sex网站,对此分享行为,我表示高度赞赏.   好了,言归正题,讲点技术问题: CYQ.Data 的使用操作方式,已经有相关文章介绍了,就不再介绍了. 本节就讲一下实现原理,具体源码,可直接下载开源的V4.0可以学习. 下载地址:http://www.cy

反射应用之一:根据控件名、属性名进行取值和赋值

控件 '必须引用命名空间System.Reflection,System.ComponentModel '以下根据控件名和属性名取值 Public Function GetValueControlProperty(ByVal ClassInstance As Object, ByVal ControlName As String, ByVal PropertyName As String) As Object Dim Result As Object Dim myType As Type = C