配色: 字号:
优化反射性能的总结
2017-03-31 | 阅:  转:  |  分享 
  
优化反射性能的总结反射是一种很重要的技术,然而它与直接调用相比性能要慢很多,因此如何优化反射性能也就成为一个不得不面对的问题。目前最常见的优
化反射性能的方法就是采用委托:用委托的方式调用需要反射调用的方法(或者属性、字段)。那么如何得到委托呢?目前最常见也就是二种方
法:Emit,ExpressionTree。其中ExpressionTree可认为是Emit方法的简化版本,所以Emit是最根
本的方法,它采用在运行时动态构造一段IL代码来包装需要反射调用的代码,这段动态生成的代码满足某个委托的签名,因此最后可以采用委托的
方式代替反射调用。http://www.cnblogs.com/fish-li/archive/2013/02/18/29162
53.html回到顶部用Emit方法优化反射如果我们需要设计自己的数据访问层,那么就需要动态创建所有的数据实体对象,尤其是还要为每
个数据实体对象的属性赋值,这里就要涉及用反射的方法对属性执行写操作,为了优化这种反射场景的性能,我们可以用下面的方法来实现:现在
可以用下面的测试代码检验委托调用带来的性能改进:我用VS2008(.net3.5,CLR2.0)测试可以得到以下结果
:从结果可以看出:1.反射调用所花时间是直接调用的2629倍,2.反射调用所花时间是Emit生成的Set委托代码的82倍,3
.运行Emit生成的Set委托代码所花时间是直接调用的31倍。虽然Emit比直接调用还有30倍的差距,但还是比反射调用快80倍
左右。有意思的是,同样的代码,如果用VS2012(.net4.5,CLR4.0)测试可以得到以下结果:感谢htt
p://www.cnblogs.com/lemontea/archive/2013/02/04/2891281.htmlzhang
weiwen在博客中展示了CRL4.0对反射的性能改进,在他的博客中还提供了一种采用表达式树的优化版本,以及包含一个泛型的强类
型的版本。http://www.cnblogs.com/fish-li/archive/2013/02/18/2916253.h
tml回到顶部Delegate.CreateDelegate也能创建委托如果我们观察CreatePropertySetter的实现
代码,发现这个方法的本质就是创建一个委托:publicstaticSetValueDelegateCreateProper
tySetter(PropertyInfoproperty){//.....省略前面已贴过的代码return(SetV
alueDelegate)dm.CreateDelegate(typeof(SetValueDelegate));}看到这里,让我
想起Delegate.CreateDelegate方法也能创建一个委托,例如:OrderInfotestObj=newO
rderInfo();PropertyInfopropInfo=typeof(OrderInfo).GetProperty(
"OrderID");Actionsetter=(Action>)Delegate.CreateDelegate(typeof(Action),null,
propInfo.GetSetMethod());setter(testObj,123);显然,这是一种很直观的方法,可以得到一
个强类型的委托。然而,这种方法仅限有一种适用场景:明确知道要访问某个类型的某个属性或者方法,因为我们要提供类型参数。例如:我要
写个关键字过滤的HttpMoudle,它需要修改HttpRequest.Form对象的IsReadOnly属性,由于IsReadO
nly在NameObjectCollectionBase类型中已申明为protected访问级别,所以我只能反射操作它了,而且还需
要很频繁的设置它。在绝大部分反射场景中,例如数据访问层中从DataReader或者DataRow加载数据实体,我们不可能事先知道
要加载哪些类型,更不可能知道要加载哪些数据成员,因此就不可能给泛型委托的类型参数赋值,这个方法看起来也就行不通了。如果您不信的话,
可以看下面修改后的代码:OrderInfotestObj=newOrderInfo();PropertyInfopro
pInfo=typeof(OrderInfo).GetProperty("OrderID");//Actionfo,int>setter=(Action)Delegate.CreateDelegate
(//typeof(Action),null,propInfo.GetSetMethod())
;Actionsetter=(Action)Delegate
.CreateDelegate(typeof(Action),null,propInfo.G
etSetMethod());setter(testObj,123);Console.WriteLine(testObj.Ord
erID);虽然能通过编译,但会在运行时报错:在很多时候,我们只能在运行时得到以Type对象表示的类型,接受object类型才是通
用的解决方案。然而,前面的代码证明了我们不能简单将委托类型从Action修改为Actionect,object>。真的没有办法了吗?虽然Emit已是很成熟的优化方案,可我还是希望试试Delegate.Create
Delegate!http://www.cnblogs.com/fish-li/archive/2013/02/18/2916
253.html回到顶部用Delegate.CreateDelegate优化反射当我们用Delegate.CreateDelega
te从一个MethodInfo对象创建委托时,委托的签名必须和MethodInfo表示的方法签名相匹配(有可能不一致),所以这种方
法得到的委托注定是一种强类型的委托。现在的问题是:我们在运行时构造与指定MethodInfo匹配的委托,如何将Type对象转换成泛
型委托的类型参数?为了解决这个问题,我采用了泛型类来解决泛型委托的类型参数问题:publicclassSetterWrap
per{privateAction_setter;p
ublicSetterWrapper(PropertyInfopropertyInfo){if(propertyInfo
==null)thrownewArgumentNullException("propertyInfo");if(p
ropertyInfo.CanWrite==false)thrownewNotSupportedException("
属性不支持写操作。");MethodInfom=propertyInfo.GetSetMethod(true);_set
ter=(Action)Delegate.CreateDelegate(typeof(Act
ion),null,m);}publicvoidSetValue(TTarget
target,TValueval){_setter(target,val);}我用泛型类把Delegate.Creat
eDelegate的问题解决了,但是如何创建这个类型的实例呢?可以用Type.MakeGenericType()方法来解决:pu
blicstaticobjectCreatePropertySetterWrapper(PropertyInfoprope
rtyInfo){if(propertyInfo==null)thrownewArgumentNullExcept
ion("propertyInfo");if(propertyInfo.CanWrite==false)thrown
ewNotSupportedException("属性不支持写操作。");MethodInfomi=propertyIn
fo.GetSetMethod(true);if(mi.GetParameters().Length>1)throw
newNotSupportedException("不支持构造索引器属性的委托。");TypeinstanceType=
typeof(SetterWrapper<,>).MakeGenericType(propertyInfo.DeclaringTy
pe,propertyInfo.PropertyType);returnActivator.CreateInstance(i
nstanceType,propertyInfo);}现在问题并没有结束,我又如何调用那些泛型类型实例的委托呢?这里还有另一个问
题要解决:调用方法需要支持object类型(满足通用性)。我想到了定义一个接口来解决:publicinterfaceISet
Value{voidSet(objecttarget,objectval);}然后让SetterWrapper实现ISe
tValue接口:publicclassSetterWrapper:ISetValue
{//.....省略前面已贴过的代码voidISetValue.Set(objecttarget,objectva
l){_setter((TTarget)target,(TValue)val);}}还有前面的CreateProperty
SetterWrapper方法也需要再次调整返回值类型:publicstaticISetValueCreateProper
tySetterWrapper(PropertyInfopropertyInfo){//.....省略前面已贴过的代码r
eturn(ISetValue)Activator.CreateInstance(instanceType,propertyI
nfo);}考虑到有些特定场景下需要用反射的方式重复操作某一个属性,使用强类型的方法可以避免拆箱装箱,所以我保留了前面的SetVa
lue方法,让它提供更好的性能,满足一些特定场景的需要。因此,现在的SetterWrapper类型有二种使用方法,可以提供二种性能
不同的实现方法。现在可以增加二段测试代码来测试它的性能了:Console.Write("泛型委托花费时间:");
SetterWrappersetter3=newSetterWrapperfo,int>(propInfo);Stopwatchwatch4=Stopwatch.StartNew();for(i
nti=0;iStop();Console.WriteLine(watch4.Elapsed.ToString());Console.Write
("通用接口花费时间:");ISetValuesetter4=GetterSetterFactory.Crea
tePropertySetterWrapper(propInfo);Stopwatchwatch5=Stopwatch.St
artNew();for(inti=0;i3);watch5.Stop();Console.WriteLine(watch5.Elapsed.ToString());测试结
果如下:测试结果表明:强类型的泛型委托的速度比Emit生成的Set委托要快,但是基于通用接口的方法调用由于多了一层包装就比Emit
方案要略慢一点。http://www.cnblogs.com/fish-li/archive/2013/02/18/291625
3.html回到顶部完整的属性优化方案前面介绍了为属性赋值这类反射案例的优化方案,那么怎么优化读取属性的反射操作呢?其实思路差不
多:1.在泛型类中调用Delegate.CreateDelegate,得到一个Func,2.
定义一个IGetValue接口,提供一个方法:objectGet(objecttarget);3.让泛型类实现IGetVa
lue接口4.提供一个工厂方法实例化泛型类的实例。相关代码如下:publicinterfaceIGetValue{obj
ectGet(objecttarget);}publicstaticclassGetterSetterFactory{
publicstaticIGetValueCreatePropertyGetterWrapper(PropertyInfo
propertyInfo){if(propertyInfo==null)thrownewArgumentNull
Exception("propertyInfo");if(propertyInfo.CanRead==false)th
rownewInvalidOperationException("属性不支持读操作。");MethodInfomi=p
ropertyInfo.GetGetMethod(true);if(mi.GetParameters().Length>0
)thrownewNotSupportedException("不支持构造索引器属性的委托。");Typeinsta
nceType=typeof(GetterWrapper<,>).MakeGenericType(propertyInfo.D
eclaringType,propertyInfo.PropertyType);return(IGetValue)Activ
ator.CreateInstance(instanceType,propertyInfo);}}publicclassG
etterWrapper:IGetValue{privateFuncTValue>_getter;publicGetterWrapper(PropertyInfopropertyInfo)
{if(propertyInfo==null)thrownewArgumentNullException("pro
pertyInfo");if(propertyInfo.CanRead==false)thrownewInvali
dOperationException("属性不支持读操作。");MethodInfom=propertyInfo.Get
GetMethod(true);_getter=(Func)Delegate.Create
Delegate(typeof(Func),null,m);}publicTValu
eGetValue(TTargettarget){return_getter(target);}objectIGe
tValue.Get(objecttarget){return_getter((TTarget)target);}}前面
的代码优化了实例属性的反射读写性能问题,但是还有极少数时候我们还需要处理静态属性,那么我们还需要再定义二个泛型类来解决:publ
icclassStaticGetterWrapper:IGetValue{privateFuncalue>_getter;//............}publicclassStaticSetterWrapperValue>:ISetValue{privateAction_setter;//..........
..}前面看到的工厂方法也要调整,完整代码如下:publicstaticISetValueCreatePropertySe
tterWrapper(PropertyInfopropertyInfo){if(propertyInfo==null
)thrownewArgumentNullException("propertyInfo");if(propertyIn
fo.CanWrite==false)thrownewNotSupportedException("属性不支持写操作。
");MethodInfomi=propertyInfo.GetSetMethod(true);if(mi.GetPa
rameters().Length>1)thrownewNotSupportedException("不支持构造索引器
属性的委托。");if(mi.IsStatic){TypeinstanceType=typeof(StaticSe
tterWrapper<>).MakeGenericType(propertyInfo.PropertyType);return
(ISetValue)Activator.CreateInstance(instanceType,propertyInfo);
}else{TypeinstanceType=typeof(SetterWrapper<,>).MakeGeneri
cType(propertyInfo.DeclaringType,propertyInfo.PropertyType);ret
urn(ISetValue)Activator.CreateInstance(instanceType,propertyInf
o);}}http://www.cnblogs.com/fish-li/archive/2013/02/18/2916253.h
tml回到顶部委托方案的后续问题前面的代码解决了属性的读写问题,然而使用它们还很不方便:每次都要创建一个ISetValue接口的实
例,再调用它的方法。其实这也是委托方案共有的问题:我们需要为每个属性的读写操作分别创建不同的委托,而且委托太零散了。如何将属性与
创建好的委托关联起来呢?(创建委托也是需要时间的)我想所有人都会想到用字典来保存。是的,好像也只有这一种方法了。为了提高性能,我改
进了工厂类,缓存了包含委托的实例,为了方便使用前面的方法,我提供了一些扩展方法:publicstaticclassGett
erSetterFactory{privatestaticreadonlyHashtables_getterDict=
Hashtable.Synchronized(newHashtable(10240));privatestaticrea
donlyHashtables_setterDict=Hashtable.Synchronized(newHashtab
le(10240));internalstaticIGetValueGetPropertyGetterWrapper(Pr
opertyInfopropertyInfo){IGetValueproperty=(IGetValue)s_gett
erDict[propertyInfo];if(property==null){property=CreateP
ropertyGetterWrapper(propertyInfo);s_getterDict[propertyInfo]=
property;}returnproperty;}internalstaticISetValueGetPrope
rtySetterWrapper(PropertyInfopropertyInfo){ISetValueproperty
=(ISetValue)s_setterDict[propertyInfo];if(property==null){
property=CreatePropertySetterWrapper(propertyInfo);s_setterDi
ct[propertyInfo]=property;}returnproperty;}}publicstaticc
lassPropertyExtensions{publicstaticobjectFastGetValue(thisP
ropertyInfopropertyInfo,objectobj){if(propertyInfo==null
)thrownewArgumentNullException("propertyInfo");returnGetterS
etterFactory.GetPropertyGetterWrapper(propertyInfo).Get(obj);}p
ublicstaticvoidFastSetValue(thisPropertyInfopropertyInfo,ob
jectobj,objectvalue){if(propertyInfo==null)thrownewAr
gumentNullException("propertyInfo");GetterSetterFactory.GetPrope
rtySetterWrapper(propertyInfo).Set(obj,value);}}说明:我在缓存的设计上并没有使
用泛型Dictionary,而是使用了Hashtable。我承认在简单的单线程测试中,Dictionary要略快于Hashtabl
e。再来测试一下FastSetValue的性能吧,毕竟大多数时候我会使用这个扩展方法。我又在测试代码中增加了一段:propI
nfo.FastSetValue(testObj,123);Console.Write("FastSet花费时间:
");Stopwatchwatch6=Stopwatch.StartNew();for(inti=0;iunt;i++)propInfo.FastSetValue(testObj,123);watch6.Stop();Cons
ole.WriteLine(watch6.Elapsed.ToString());测试结果如下:测试结果表明:虽然通用接口ISet
Value将反射性能优化了37倍,但是最终的FastSetValue将这个数字减少到还不到7倍(在CLR4中还不到5倍)。看到这
个结果您是否也比较郁闷:优化了几十倍的结果,最后却丢了大头,只得到一个零头!中间那30倍的时间是哪里消耗了?1.Hashtab
le的查找时间。2.代码的执行路径变长了。代码的执行路径变长了,我想所有人应该都能接受:为了简化调用并配合缓存一起工作,代码的
执行路径确实变长了。Hashtable的查找时间应该很快吧?您是不是也这样想呢?为了看看Hashtable的查找时间,我又加
了一点测试代码:Hashtabletable=newHashtable();table[propInfo]=new
object();Console.Write("Hashtable花费时间:");Stopwatchwatch7=
Stopwatch.StartNew();for(inti=0;ial=table[propInfo];}watch7.Stop();Console.WriteLine(watch7.Elap
sed.ToString());现在运行测试代码的结果如下:确实,大部分时间消耗在Hashtable的查找上!http://ww
w.cnblogs.com/fish-li/archive/2013/02/18/2916253.html回到顶部缓存的线程并发问
题集合不仅仅只有查找开销,在多线程环境中,我们还要考虑并发性。看到许多人做性能测试时,总是喜欢写个控制台程序,然后再来个for循
环,执行多少万次!我认为这样的结果只能反映代码在单线程环境下的性能,在多线程下,结果可能会有较大的差别,当然了,多线程测试的确很
复杂,也很难得到准确的数字。但是我们的设计不能不考虑多线程下的并发问题。虽然我也在单线程环境下测试过Dictionaryey,TValue>的性能,的确要比Hashtable略好点。但是MSDN上对Dictionary的线程安全的描述是这样的:此
类型的公共静态(在VisualBasic中为Shared)成员是线程安全的。但不能保证任何实例成员是线程安全的。只要不修
改该集合,Dictionary<(Of<(TKey,TValue>)>)就可以同时支持多个阅读器。即便如此,从头到尾对一个集
合进行枚举本质上并不是一个线程安全的过程。当出现枚举与写访问互相争用这种极少发生的情况时,必须在整个枚举过程中锁定集合。若要允许多
个线程访问集合以进行读写操作,则必须实现自己的同步。而MSDN对Hashtable的线程安全的描述却是:Hashtable是
线程安全的,可由多个读取器线程和一个写入线程使用。多线程使用时,如果只有一个线程执行写入(更新)操作,则它是线程安全的,从而允许进
行无锁定的读取(若编写器序列化为Hashtable)。若要支持多个编写器,如果没有任何线程在读取Hashtable对象,则对
Hashtable的所有操作都必须通过Synchronized方法返回的包装完成。从头到尾对一个集合进行枚举本质上并不是
一个线程安全的过程。即使一个集合已进行同步,其他线程仍可以修改该集合,这将导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在
整个枚举过程中锁定集合,或者捕捉由于其他线程进行的更改而引发的异常。显然,二个集合都不能完全支持多线程的并发读写。虽然Hasht
able提供同步包装的线程安全版本,但是内部还是在使用锁来保证同步的!没办法,在多线程环境中,任何复杂数据结构都有线程安全问题。
如何保证集合在并发操作中数据的同步呢?是lock还是ReaderWriterLock?显然前者的实现较为简单,所以它成了绝大多数人
的首选。在.net4中,ConcurrentDictionary是另一个新的首选方法。由于Dictionary只支持并发的读操作
,所以只要涉及到写操作,它就不安全了,因此最安全地做法也只好在读和写操作上都加lock,否则就不安全了。而Hashtable
则不同,它的内部数据结构支持一个线程写入的同时允许多个线程并发读取,所以只要在写操作上加lock就可以实现线程同步,Hashta
ble的线程安全版本也就是这样实现的。这也是我选择Hashtable的原因。http://www.cnblogs.com/fis
h-li/archive/2013/02/18/2916253.html回到顶部小结在这篇博客中,我演示了二种不同的反射优化方法:
1.基于Emit的动态生成符合委托签名的IL代码。2.使用Delegate.CreateDelegate直接创建委托。这是二
种截然不同的思路:1.Emit方法,需要先定义一个委托签名,然后生成符合委托签名的IL代码。2.CreateDelegate可
以直接生成委托,但需要借用泛型类解决委托的类型参数问题,最后为了能通用,需要以接口方式调用强类型委托。虽然我们可以使用任何一种方
法得到委托,但是我们需要操作多少属性呢?显然这是一个无解的问题,我们只能为每个属性创建不同的委托。所以新的问题也随之产生:我们如何
保存那些委托?如何让它们与属性关联起来?Dictionary或者Hashtable或许是较好的选择(.net3.5),然而,这
些对象内部的数据结构在查找时,并不是零成本,它们会消耗优化的大部分成果。另外,在实现缓存委托的问题上,并发问题也是值得我们考虑的,
不高效的并发设计还会让优化的成果继续丢失!所以,我认为优化反射是个复杂问题,至少有3个环节是需要考虑的:1.如何得到委托?2.
如何缓存委托?3.如何支持并发?得到委托是容易的,但它只是一个开始!问题回顾在http://www.cnblogs.com
/fish-li/archive/2013/02/18/2916253.html上篇博客中,我介绍了优化反射的第一个步骤:用委托调
用代替直接反射调用。然而,那只是反射优化过程的开始,因为新的问题出现了:如何保存大量的委托?如果我们将委托保存在字典集合中,会发
现这种设计会浪费较多的执行时间,因为这种设计会引发三个新问题:1.代码的执行路径变长了。2.字典查找是有成本开销的。3.字典
集合的并发读写需要锁定,会影响并发性。再来回顾一下上次的测试结果吧:虽然通用接口ISetValue将反射性能优化了37倍,但是最
终的FastSetValue将这个数字减少到还不到7倍(在CLR4中还不到5倍)。难道您不觉得遗憾吗?再看看直接调用与反射调用的
对比,它们的速度相差了上千倍!http://www.cnblogs.com/fish-li/archive/2013/02/24
/2924673.html回到顶部能不能不使用委托?既然委托最后引出了三个难以解决的问题,导致优化后速度比直接调用差距太远,那我们
能不能不使用委托呢?委托调用并不是优化反射的唯一方案,我们还有其它方法,之所以委托调用能成为常见的优化方案是因为它比较简单。假
如我需要用客户端提交的数据来填充某个数据对象,考虑到代码的通用性,我会用反射写成这样://////从Htt
pRequest加载obj所需的数据///
///>///publicstaticvoidLoadDataFromHtt
pRequest(HttpRequestrequest,objectobj){PropertyInfo[]propert
ies=obj.GetType().GetProperties();foreach(PropertyInfopinp
roperties){//这里只是示意代码,假设数据处理不会有异常。objectval=Convert.Chang
eType(request[p.Name],p.PropertyType);p.FastSetValue(obj,val);
}}如果我事先知道要加载已知的数据类型,代码会写成这样:publicstaticvoidLoadDataFromHttp
Request(HttpRequestrequest,OrderInfoorder){//这里只是示意代码,假设数据处理
不会有异常。order.OrderID=int.Parse(request["OrderID"]);order.Order
Date=DateTime.Parse(request["OrderDate"]);order.SumMoney=dec
imal.Parse(request["SumMoney"]);order.Comment=request["Comment
"];order.Finished=bool.Parse(request["Finished"]);}显然,第二段代码运行效
率更快(尽管第一段代码调用FastSetValue优化了速度)。大家都知道反射性能较差,直接调用性能最好,那么能不能在运行时不使
用反射呢?的确,使用反射是因为我们事先不知道要处理哪些类型的对象,因此不得不用反射,另外,反射的代码也更通用,写一个方法可以加载
所有的数据类型,可认为是一劳永逸的方法。不过,就算我们事先不知道要处理哪些对象类型,但是只要使用反射,我们完全可以知道任何一个类型
包含哪些数据成员,还能知道这些数据成员的数据类型,这一点不用怀疑吧?既然我们用反射可以知道所有的类型定义信息,我们是否可以参照代码
生成器的思路去生成代码呢?我们可以参照前面第二段代码,为【需要处理的类型】生成直接调用的代码,这样不就彻底解决了反射性能问题了吗?
生成代码的过程,其实也就是个字符串的拼接过程,难度并不大,只是比较复杂而已。如果前面的答案都是肯定的,那么现在只有一个问题了:我
们能在运行时执行拼接生成的字符串代码吗?答案也是肯定的:能!http://www.cnblogs.com/fish-li/ar
chive/2013/02/24/2924673.html回到顶部CodeDOM:在运行时编译代码回忆一下我们编写的ASPX页面,
它们并不是C#代码,它们本质上就是一个文本文件,我们可以写入一些HTML标签,还有些标签上加了runat="server"属性
,我们还可以在页面中插入一些C#代码片段,尽管它们不是我们编译后的DLL文件,然而它们就是运行起来了!要知道ASP.NET不是AS
P,ASP是解释性的脚本语言,而ASP.NET是以编译方式运行的,所以,每个ASPX页面文件最后都是运行编译后的结果。假设我有下
面一段文本(文本的内容是一段C#代码):usingSystem;usingSystem.Collections.Generi
c;usingSystem.Text;usingSystem.Reflection;namespaceOptimizeRef
lection{publicclassDemoClass{publicintId{get;set;}pub
licstringName;publicintAdd(inta,intb){returna+b;}}
publicclass用户手册{publicstaticvoidMain(){//OptimizeRefle
ction这个类库提供了一些扩展方法,它们用于优化常见的反射场景//下面是一些相关的演示示例。//对于属性的读写操作、
方法的调用操作,还提供了性能更好的强类型(泛型)版本,可参考Program.csTypeinstanceType=type
of(DemoClass);PropertyInfopropertyInfo=instanceType.GetProper
ty("Id");FieldInfofieldInfo=instanceType.GetField("Name");Me
thodInfomethodInfo=instanceType.GetMethod("Add");//1.创建实例对象
DemoClassobj=(DemoClass)instanceType.FastNew();//2.写属性pro
pertyInfo.FastSetValue(obj,123);propertyInfo.FastSetValue2(obj,
123);//3.读属性inta=(int)propertyInfo.FastGetValue(obj);int
b=(int)propertyInfo.FastGetValue2(obj);//4.写字段fieldInfo.Fa
stSetField(obj,"FishLi");//5.读字段strings=(string)fieldInf
o.FastGetValue(obj);//6.调用方法intc=(int)methodInfo.FastInvok
e(obj,1,2);intd=(int)methodInfo.FastInvoke2(obj,3,4);Con
sole.WriteLine("a={0};b={1};c={2};d={3};s={4}",a,b,c,d,s
);}}}您可以把上面这段文本想像成前面第二个版本的LoadDataFromHttpRequest方法,如果我们在运行时使用反
射也能生成那样的代码,现在就差把它编译成程序集了。下面的代码演示了如何将一段文本编译成程序集的过程:stringcode=
null;//1.生成要编译的代码。(示例为了简单直接从程序集内的资源中读取)Streamstram=typeof(Co
deDOM).Assembly.GetManifestResourceStream("TestOptimizeReflectio
n.用户手册.txt");using(StreamReadersr=newStreamReader(stram)){
code=sr.ReadToEnd();}//Console.WriteLine(code);//2.设置编译参数,主要
是指定将要引用哪些程序集CompilerParameterscp=newCompilerParameters();cp.G
enerateExecutable=false;cp.GenerateInMemory=true;cp.Reference
dAssemblies.Add("System.dll");cp.ReferencedAssemblies.Add("Optimi
zeReflection.dll");//3.获取编译器并编译代码//由于我的代码使用了【自动属性】特性,所以需要C#.
3.5版本的编译器。//获取与CLR匹配版本的C#编译器可以这样写:CodeDomProvider.CreateProvider
("CSharp")Dictionarydict=newDictionary,string>();dict["CompilerVersion"]="v3.5";dict["WarnAsError"]
="false";CSharpCodeProvidercsProvider=newCSharpCodeProvider(
dict);CompilerResultscr=csProvider.CompileAssemblyFromSource(c
p,code);//4.检查有没有编译错误if(cr.Errors!=null&&cr.Errors.HasErr
ors){foreach(CompilerErrorerrorincr.Errors)Console.Write
Line(error.ErrorText);return;}//5.获取编译结果,它是编译后的程序集Assemblyasm
=cr.CompiledAssembly;整个过程分为5个步骤,它们已用注释标识出来了,这里不再重复了。http://www
.cnblogs.com/fish-li/archive/2013/02/24/2924673.html回到顶部如何调用编译结果前
面的代码把一段文本字符串编译成了程序集,现在还有最后一个问题:如何调用编译结果?答案:有二种方法,1.直接调用方法。2.实例
化程序集中的类型,以接口方式调用方法。其实这二种方法都需要使用反射,用反射定位到要调用的类型和方法。第一种方法要求在生成代码时,
生成的类名和方法名是明确的,在调用方法时,我们有二个选择:1.用反射的方式调用(这里只是一次反射)。2.为方法生成委托(用上篇
博客介绍的方法),然后基于委托调用。第二种方法要求在生成代码时,首先要定义一个接口,保证生成的代码能实现指定的接口,然而用反射找
到要调用的类型名称,用反射或者委托调用构造方法创建类型实例,最后基于接口去调用。我们熟悉的ASPX页面就是采用了这种方式来实现的。
这二种方法也可以这样区分:1.如果生成的方法是静态方法,应该选择第一种方法。2.如果生成的方法是实例方法,那么选择第二种方法
是合理的。对于前面的示例,我采用了第一种方法了,因为类名和方法名称都是事先确定的而且实现起来比较简单。//6.找到目标方法
,并调用Typet=asm.GetType("OptimizeReflection.用户手册");MethodInfome
thod=t.GetMethod("Main");method.Invoke(null,null);能不能不使用委托?如何
用好CodeDOM?在这篇博客中我不知道把它们安排在哪里较为合适,算了,还是把答案留给下篇博客吧。在前二篇博客中,我分别介绍了二
种优化反射的方法:1.http://www.cnblogs.com/fish-li/archive/2013/02/18/291
6253.htmlDelegate:委托。2.http://www.cnblogs.com/fish-li/archive/20
13/02/24/2924673.htmlCodeDOM:动态代码生成。这是二种截然不同的方法,性能的差距也很大。今天的博客将着重
比较它们的优缺点,以及给出它们的使用建议。http://www.cnblogs.com/fish-li/archive/2013
/03/03/2941911.html回到顶部用Delegate优化反射的缺点在评价委托方案时,我认为有必要细分一下委托方案:1.
强类型委托,例如:Action2.弱类型委托,例如:Actiont>它们的优点分别是:强类型委托:速度快,已经最接近直接调用的性能,然而它的缺点是不通用。弱类型委托:比较通用,且经过一些代
码封装后,使用方便,但是封装后的性能会变差。http://www.cnblogs.com/fish-li/archive/20
13/03/03/2941911.html回到顶部用Delegate优化反射的优点优点有二个:1.实现简单,不管是使用Emit,
ExpressionTree还是CreateDelegate,代码量都不大。2.方法通用,使用弱类型委托,我们可以封装出很容易
使用的API,且适用于任何项目。http://www.cnblogs.com/fish-li/archive/2013/03/0
3/2941911.html回到顶部用CodeDOM优化反射的优点最大的,也是唯一的优点就是:性能好。由于生成的是直接调用的代码,
因此最终运行的是直接调用的代码,所以没有性能损耗。另外,代码生成器可以决定最终生成的代码质量,代码生成器越优秀,代码的性能也会更优
秀。注意:当使用这种技术时,不同人可能会有不同的使用方法,最终可以得到性能不同的结果,(理论上)最坏情况下可能比委托还差。如果
希望借助这种优化方式实现最好的性能,需要做好二件事情:1.保证最终生成的代码质量是最优的。2.编译方式的设计要合理(用好Cod
eDOM)。如何保证最终生成的代码质量是最优的,我给不了建议,需要您自己去思考,我们接着讨论第2点。http://www.cn
blogs.com/fish-li/archive/2013/03/03/2941911.html回到顶部如何用好CodeDOM?
虽然采用动态编译技术,我们可以生成直接调用的代码来代替反射调用,这样就不会有任何性能损失。但是,还有一个问题也是需要考虑的:我该以
什么粒度去生成代码?1.是为每个反射调用生成代码?2.还是为每个类型批量生成一段代码?3.还是为一堆类型大批量的生成一批代码
?由于动态编译的结果并不能直接调用,我们只能借助委托或者接口的方式去调用,所以如果每次代码生成的粒度较小,将会产生大量的程序集,
也会消耗较多的编译器启动时间,因此,这并不是高效的做法。高效的做法应该是一次尽可能生成较多的代码。除此之外,还有一个问题也要考虑
:当需要循环调用编译结果时,该怎么办?对于这类场景,我建议在生成代码时,把循环过程直接生成出来,最终只用一次调用编译结果完成整个调
用过程。例如我们可以为数据访问层生成这样类似的代码,把循环、创建实体对象,以及给属性赋值的所有操作全部包含进来:publics
taticListLoadProduct(DbDataReaderreader){Listt>list=newList();while(reader.Read()){Productp
=newProduct();p.ProductID=(int)reader["ProductID"];p.Produ
ctName=reader["ProductName"].ToString();p.CategoryID=(int)re
ader["CategoryID"];p.Unit=reader["Unit"].ToString();p.UnitPri
ce=(decimal)reader["UnitPrice"];p.Remark=reader["Remark"].To
String();p.Quantity=(int)reader["Quantity"];list.Add(p);}re
turnlist;}如果我们生成了这样的代码,最后只需要一次调用,就可以代替以前上百次的委托调用以及缓存查找,锁的冲突也会减少到
最低。http://www.cnblogs.com/fish-li/archive/2013/03/03/2941911.htm
l回到顶部用CodeDOM优化反射的缺点缺点有三个:1.方法不通用,需要针对不同的类型,不同的数据源生成不同的直接调用代码,因此
难以通用化。2.复杂性较高,由于是生成直接调用的代码,且数据类型及格式未知,因此需要周密的考虑各种情况,复杂性也随之增高。3.
难以封装,由于编译的结果是一个程序集,它并不能直接调用,还需要借助其它的方式来调用,所以难以实现较为通用的封装。http://w
ww.cnblogs.com/fish-li/archive/2013/03/03/2941911.html回到顶部能不能不使用委
托?既然我们可以在运行时动态生成代码并编译它们,达到代替反射的目标,因此也就不需要委托调用的优化方法了。那么,委托还有意义吗?或
者说:优化反射时能不能不使用委托?在上篇博客中,我演示过动态编译的方法。由于动态编译的结果是一个程序集,它本身是不能直接调用,我
们需要采用其它的方法去调用它。那篇博客给大家介绍了二种方法,其中一种方法就是用委托去调用程序集中的方法。由于那些在运行时生成的代码
是由我们的代码生成的,方法的签名我们可以控制,所以,这时调用Delegate.CreateDelegate方法您不会遇到任何麻
烦,因此,通过强类型的委托来调用CodeDOM的编译结果,这种配合会非常方便。正是由于这个原因,当您选择生成static类型的方
法时,委托还是必须的,此时委托和CodeDOM将是一种共存关系。如果您在生成代码时采用了接口的设计方案,那么委托就没有必要使用了
。http://www.cnblogs.com/fish-li/archive/2013/03/03/2941911.html回
到顶部根据反射密集程度选择优化方法优化反射,到底是选择CodeDOM,还是选择Delegate?我认为要按不同的反射密集程度分开
讨论。1.反射密集程度低:例如:一次HTTP请求过程,我们的代码只需要一二次反射操作,或者对于桌面程序来说,在响应用户点击事件时,使用了几次反射调用。在这类场景中,反射的密集程度就可认为是很低的。那么这种情况下该如何优化呢?我的答案是:优不优化都无所谓,因为反射并不是慢得不能接受。反射的速度到底有多慢?我们还是来看一下以前做过的测试吧:从这张图片(来源于本系列的第一篇)可以看出,用反射的方式执行属性赋值操作,就算运行1000000次,也只花了1.2秒!要知道我的测试机器是3年前买的笔记本电脑,如果换成目前专业的服务器,消耗的时间会更少,因此,这类反射的优化价值不大。当然了,如果您愿意优化它们那也不是件坏事。2.反射密集程度高:例如,数据访问层的应用中,当一次加载一个实体列表时,反射次数是分页数量乘以字段数量,再加上创建实体对象数量。这个数量很容易达到百次级别,而且一次HTTP请求过程中,可能需要加载多种数据,那么反射次数就很可观了。我们经常感觉各种序列化和反序列化程序的执行效率不高,这与反射有着很直接的关系。不过,我们通常不需要编写序列化反序列化程序,也只能被迫接受它们的性能了。因此,对于反射密集程度很高的代码,如果优化手段不理想,肯定会影响性能。3.当处于前二者之间的密集程度。由于这类场景实在是无法定性衡量,而且不同人对性能敏感程度也不一样,或者由于不同的应用对性能的要求也不同。因此,这类场景的范围只能靠自己去评估了,优化方式也只能是自行选择了:1.关注性能的话,就选择CodeDOM,2.否则就选择Delegate吧,毕竟这种方法使用简单。http://www.cnblogs.com/fish-li/archive/2013/03/03/2941911.html回到顶部CodeDOM优化的误区1.CodeDOM真能让程序的性能提升千倍吗?根据前面的截图,我们知道直接调用比反射调用的性能要提升千倍,因此是不是可以认为采用动态编译的方法,程序的性能就能提升千倍?答案是否定的。举例来说,拿创建实体对象的场景来说,虽然反射调用所花时间和直接调用时间差了千倍,即使我们用动态编译代替了反射,但是给属性赋值前,我们需要为那些属性获取数据。然而,获取数据的操作极有可能比反射更慢,因此,对于整个过程来说,我们能优化的只是其中的一小部分,所以,当我们测试整个过程时,性能不会提升到千倍。性能提升多少倍,取决于反射在整个过程中所花时间的比例。2.CodeDOM方案一定比Delegate方案快。答案也是否定的,前面已经解释过了,如果您为每个反射调用去生成一个方法(委托的思路),那么最后还是需要一个委托或者一个接口来调用,而且此时还要加上编译器的启动时间,最终的性能将比委托更慢。http://www.cnblogs.com/fish-li/archive/2013/03/03/2941911.html回到顶部反射优化的总结反射优化的根本方法只有一条路:避开反射。然而,避开的方法可分为二种:1.http://www.cnblogs.com/fish-li/archive/2013/02/18/2916253.html用委托去调用。(绕弯子)2.http://www.cnblogs.com/fish-li/archive/2013/02/24/2924673.html生成直接调用代码,替代反射调用。(直截了当)这二种方法都有优缺点,我认为选择哪种方法应该根据反射场景来决定:1.调用目标明确(名称和类型都是已知):强类型委托方法是较好的选择。2.调用目标不明确,且调用程度密集:动态编译方法是最好的选择。3.其它情况:可以用弱类型委托,或者不优化。
献花(0)
+1
(本文系关平藏书首藏)