NET 性能优化方法总结.docx
《NET 性能优化方法总结.docx》由会员分享,可在线阅读,更多相关《NET 性能优化方法总结.docx(34页珍藏版)》请在冰豆网上搜索。
NET性能优化方法总结
.NET性能优化方法总结
Ver1.0
2009-1-20
目录
1.C#语言方面...4
1.1垃圾回收...4
1.1.1避免不必要的对象创建...4
1.1.2不要使用空析构函数★...4
1.1.3实现IDisposable接口...4
1.2String操作...5
1.2.1使用StringBuilder做字符串连接...5
1.2.2避免不必要的调用ToUpper或ToLower方法...5
1.2.3最快的空串比较方法...6
1.3多线程...6
1.3.1线程同步...6
1.3.2使用ThreadStatic替代NameDataSlot★...7
1.3.3多线程编程技巧...7
1.4类型系统...8
1.4.1避免无意义的变量初始化动作...8
1.4.2ValueType和ReferenceType.8
1.4.3尽可能使用最合适的类型...9
1.5异常处理...10
1.5.1不要吃掉异常★...10
1.5.2不要吃掉异常信息★...10
1.5.3避免不必要的抛出异常...10
1.5.4避免不必要的重新抛出异常...10
1.5.5捕获指定的异常,不要使用通用的System.Exception.10
1.5.6要在finally里释放占用的资源...11
1.6反射...11
1.6.1反射分类...12
1.6.2动态创建对象...12
1.6.3动态方法调用...12
1.6.4推荐的使用原则...12
1.7基本代码技巧...13
1.7.1循环写法...13
1.7.2拼装字符串...13
1.7.3避免两次检索集合元素...13
1.7.4避免两次类型转换...14
1.7.5为字符串容器声明常量,不要直接把字符封装在双引号""里面。
...14
1.7.6用StringBuilder代替使用字符串连接符“+”.14
1.7.7避免在循环体里声明变量,...15
1.8Hashtable.15
1.8.1Hashtable机理...15
1.8.2使用HashTale代替其他字典集合类型的情形:
...16
1.9避免使用ArrayList。
1.10从XML对象读取数据...17
1.11避免使用递归调用和嵌套循环,...17
1.12使用适当的Caching策略来提高性能...17
2.Ado.Net17
2.1应用A的一些思考原则...18
2.2Connection.18
2.2.1在方法中打开和关闭连接...18
2.2.2显式关闭连接...18
2.2.3确保连接池启用...19
2.2.4不要缓存连接...19
2.3Command.19
2.3.1使用ExecuteScalar和ExecuteNonQuery.19
2.3.2使用Prepare.19
2.3.3使用绑定变量★...19
2.4DataReader20
2.4.1显式关闭DataReader20
2.4.2用索引号访问代替名称索引号访问属性...20
2.4.3使用类型化方法访问属性...20
2.4.4使用多数据集...20
2.5DataSet21
2.5.1利用索引加快查找行的效率...21
2.使用DataView..21
3.ASP.NET.21
3.1减少往返行程(ReduceRoundTrips)...21
3.2避免阻塞和长时间的作业...22
3.3使用缓存...22
3.4多线程...22
3.5系统资源...23
3.6页面处理...23
3.7ViewState.23
4.JScript24
4.1JScript性能优化的基本原则...24
4.2JScript语言本身的优化...24
4.3DOM相关...27
4.4其他...28
1.C#语言方面
1.1垃圾回收
垃圾回收解放了手工管理对象的工作,提高了程序的健壮性,但副作用就是程序代码可能对于对象创建变得随意。
1.1.1避免不必要的对象创建
由于垃圾回收的代价较高,所以C#程序开发要遵循的一个基本原则就是避免不必要的对象创建。
以下列举一些常见的情形。
1.1.1.1避免循环创建对象★
如果对象并不会随每次循环而改变状态,那么在循环中反复创建对象将带来性能损耗。
高效的做法是将对象提到循环外面创建。
1.1.1.2在需要逻辑分支中创建对象
如果对象只在某些逻辑分支中才被用到,那么应只在该逻辑分支中创建对象。
1.1.1.3使用常量避免创建对象
程序中不应出现如newDecimal(0)之类的代码,这会导致小对象频繁创建及回收,正确的做法是使用Decimal.Zero常量。
我们有设计自己的类时,也可以学习这个设计手法,应用到类似的场景中。
1.1.1.4使用StringBuilder做字符串连接
1.1.2不要使用空析构函数★
如果类包含析构函数,由创建对象时会在Finalize队列中添加对象的引用,以保证当对象无法可达时,仍然可以调用到Finalize方法。
垃圾回收器在运行期间,会启动一个低优先级的线程处理该队列。
相比之下,没有析构函数的对象就没有这些消耗。
如果析构函数为空,这个消耗就毫无意义,只会导致性能降低!
因此,不要使用空的析构函数。
在实际情况中,许多曾在析构函数中包含处理代码,但后来因为种种原因被注释掉或者删除掉了,只留下一个空壳,此时应注意把析构函数本身注释掉或删除掉。
1.1.3实现IDisposable接口
垃圾回收事实上只支持托管内在的回收,对于其他的非托管资源,例如WindowGDI句柄或数据库连接,在析构函数中释放这些资源有很大问题。
原因是垃圾回收依赖于内在紧张的情况,虽然数据库连接可能已濒临耗尽,但如果内存还很充足的话,垃圾回收是不会运行的。
C#的IDisposable接口是一种显式释放资源的机制。
通过提供using语句,还简化了使用方式(编译器自动生成try...finally块,并在finally块中调用Dispose方法)。
对于申请非托管资源对象,应为其实现IDisposable接口,以保证资源一旦超出using语句范围,即得到及时释放。
这对于构造健壮且性能优良的程序非常有意义!
为防止对象的Dispose方法不被调用的情况发生,一般还要提供析构函数,两者调用一个处理资源释放的公共方法。
同时,Dispose方法应调用System.GC.SuppressFinalize(this),告诉垃圾回收器无需再处理Finalize方法了。
1.2String操作
1.2.1使用StringBuilder做字符串连接
String是不变类,使用+操作连接字符串将会导致创建一个新的字符串。
如果字符串连接次数不是固定的,例如在一个循环中,则应该使用StringBuilder类来做字符串连接工作。
因为StringBuilder内部有一个StringBuffer,连接操作不会每次分配新的字符串空间。
只有当连接后的字符串超出Buffer大小时,才会申请新的Buffer空间。
典型代码如下:
StringBuildersb=newStringBuilder(256);
for(inti=0;i{sb.Append(Results[i]);}如果连接次数是固定的并且只有几次,此时应该直接用+号连接,保持程序简洁易读。实际上,编译器已经做了优化,会依据加号次数调用不同参数个数的String.Concat方法。例如:Stringstr=str1+str2+str3+str4; 会被编译为String.Concat(str1,str2,str3,str4)。该方法内部会计算总的String长度,仅分配一次,并不会如通常想象的那样分配三次。作为一个经验值,当字符串连接操作达到10次以上时,则应该使用StringBuilder。 这里有一个细节应注意:StringBuilder内部Buffer的缺省值为16,这个值实在太小。按StringBuilder的使用场景,Buffer肯定得重新分配。经验值一般用256作为Buffer的初值。当然,如果能计算出最终生成字符串长度的话,则应该按这个值来设定Buffer的初值。使用newStringBuilder(256)就将Buffer的初始长度设为了256。 1.2.2避免不必要的调用ToUpper或ToLower方法 String是不变类,调用ToUpper或ToLower方法都会导致创建一个新的字符串。如果被频繁调用,将导致频繁创建字符串对象。这违背了前面讲到的“避免频繁创建对象”这一基本原则。 例如,bool.Parse方法本身已经是忽略大小写的,调用时不要调用ToLower方法。 另一个非常普遍的场景是字符串比较。高效的做法是使用Compare方法,这个方法可以做大小写忽略的比较,并且不会创建新字符串。 例: conststringC_VALUE="COMPARE"; if(String.Compare(sVariable,C_VALUE,true)==0) { Console.Write("SAME"); } 还有一种情况是使用HashTable的时候,有时候无法保证传递key的大小写是否符合预期,往往会把key强制转换到大写或小写方法。实际上HashTable有不同的构造形式,完全支持采用忽略大小写的key:newHashTable(StringComparer.OrdinalIgnoreCase)。 1.2.3最快的空串比较方法 将String对象的Length属性与0比较是最快的方法:if(str.Length==0) 其次是与String.Empty常量或空串比较:if(str==String.Empty)或if(str=="") 注:C#在编译时会将程序集中声明的所有字符串常量放到保留池中(internpool),相同常量不会重复分配。1.3多线程1.3.1线程同步 线程同步是编写多线程程序需要首先考虑问题。C#为同步提供了Monitor、Mutex、AutoResetEvent和ManualResetEvent对象来分别包装Win32的临界区、互斥对象和事件对象这几种基础的同步机制。C#还提供了一个lock语句,方便使用,编译器会自动生成适当的Monitor.Enter和Monitor.Exit调用。 1.3.1.1同步粒度 同步粒度可以是整个方法,也可以是方法中某一段代码。为方法指定MethodImplOptions.Synchronized属性将标记对整个方法同步。例如:[MethodImpl(MethodImplOptions.Synchronized)]publicstaticSerialManagerGetInstance(){if(instance==null){instance=newSerialManager();}returninstance;}通常情况下,应减小同步的范围,使系统获得更好的性能。简单将整个方法标记为同步不是一个好主意,除非能确定方法中的每个代码都需要受同步保护。 1.3.1.2同步策略 使用lock进行同步,同步对象可以选择Type、this或为同步目的专门构造的成员变量。 避免锁定Type★ 锁定Type对象会影响同一进程中所有AppDomain该类型的所有实例,这不仅可能导致严重的性能问题,还可能导致一些无法预期的行为。这是一个很不好的习惯。即便对于一个只包含static方法的类型,也应额外构造一个static的成员变量,让此成员变量作为锁定对象。 避免锁定this 锁定this会影响该实例的所有方法。假设对象obj有A和B两个方法,其中A方法使用lock(this)对方法中的某段代码设置同步保护。现在,因为某种原因,B方法也开始使用lock(this)来设置同步保护了,并且可能为了完全不同的目的。这样,A方法就被干扰了,其行为可能无法预知。所以,作为一种良好的习惯,建议避免使用lock(this)这种方式。 使用为同步目的专门构造的成员变量 这是推荐的做法。方式就是new一个object对象,该对象仅仅用于同步目的。 如果有多个方法都需要同步,并且有不同的目的,那么就可以为些分别建立几个同步成员变量。1.3.1.4集合同步 C#为各种集合类型提供了两种方便的同步机制:Synchronized包装器和SyncRoot属性。//CreatesandinitializesanewArrayListArrayListmyAL=newArrayList();myAL.Add("The");myAL.Add("quick");myAL.Add("brown");myAL.Add("fox");//CreatesasynchronizedwrapperaroundtheArrayListArrayListmySyncdAL=ArrayList.Synchronized(myAL);调用Synchronized方法会返回一个可保证所有操作都是线程安全的相同集合对象。考虑mySyncdAL[0]=mySyncdAL[0]+"test"这一语句,读和写一共要用到两个锁。一般讲,效率不高。推荐使用SyncRoot属性,可以做比较精细的控制。1.3.2使用ThreadStatic替代NameDataSlot★ 存取NameDataSlot的Thread.GetData和Thread.SetData方法需要线程同步,涉及两个锁:一个是LocalDataStore.SetData方法需要在AppDomain一级加锁,另一个是ThreadNative.GetDomainLocalStore方法需要在Process一级加锁。如果一些底层的基础服务使用了NameDataSlot,将导致系统出现严重的伸缩性问题。 规避这个问题的方法是使用ThreadStatic变量。示例如下:publicsealedclassInvokeContext{[ThreadStatic]privatestaticInvokeContextcurrent;privateHashtablemaps=newHashtable();}1.3.3多线程编程技巧 1.3.3.1使用DoubleCheck技术创建对象internalIDictionaryKeyTable{get{if(this._keyTable==null){lock(base._lock){if(this._keyTable==null){this._keyTable=newHashtable();}}}returnthis._keyTable;}}创建单例对象是很常见的一种编程情况。一般在lock语句后就会直接创建对象了,但这不够安全。因为在lock锁定对象之前,可能已经有多个线程进入到了第一个if语句中。如果不加第二个if语句,则单例对象会被重复创建,新的实例替代掉旧的实例。如果单例对象中已有数据不允许被破坏或者别的什么原因,则应考虑使用DoubleCheck技术。1.4类型系统 1.4.1避免无意义的变量初始化动作 CLR保证所有对象在访问前已初始化,其做法是将分配的内存清零。因此,不需要将变量重新初始化为0、false或null。 需要注意的是:方法中的局部变量不是从堆而是从栈上分配,所以C#不会做清零工作。如果使用了未赋值的局部变量,编译期间即会报警。不要因为有这个印象而对所有类的成员变量也做赋值动作,两者的机理完全不同! 1.4.2ValueType和ReferenceType1.4.2.1以引用方式传递值类型参数 值类型从调用栈分配,引用类型从托管堆分配。当值类型用作方法参数时,默认会进行参数值复制,这抵消了值类型分配效率上的优势。作为一项基本技巧,以引用方式传递值类型参数可以提高性能。 1.4.2.2为ValueType提供Equals方法 .net默认实现的ValueType.Equals方法使用了反射技术,依靠反射来获得所有成员变量值做比较,这个效率极低。如果我们编写的值对象其Equals方法要被用到(例如将值对象放到HashTable中),那么就应该重载Equals方法。publicstructRectangle{publicdoubleLength;publicdoubleBreadth;publicoverrideboolEquals(objectob){if(obisRectangle)returnEquels((Rectangle)ob))elsereturnfalse;}privateboolEquals(Rectanglerect){returnthis.Length==rect.Length&&this.Breadth==rect.Breach;}}1.4.2.3避免装箱和拆箱 C#可以在值类型和引用类型之间自动转换,方法是装箱和拆箱。装箱需要从堆上分配对象并拷贝值,有一定性能消耗。如果这一过程发生在循环中或是作为底层方法被频繁调用,则应该警惕累计的效应。 一种经常的情形出现在使用集合类型时。例如:ArrayListal=newArrayList();for(inti=0;i<1000;i++){al.Add(i);//ImplicitlyboxedbecauseAdd()takesanobject}intf=(int)al[0];//Theelementisunboxed但是得当心!如果你像使用引用类型那么频繁的使用一个值类型的话,值类型的优势会很快被耗尽。比如,把一个值类型压到一个含有对象类型的群集。这叫做装箱,很耗用处理器周期,尤其是当你的代码在把它作为值(对它进行数学运算)和把它作为引用之间来回运行时。1.4.3尽可能使用最合适的类型•尽可能使用最合适的类型来描述数据,从而减少类型转换。•使用泛型来创建群集和其它的数据结构,这样,在运行时,它们就可以被实例化来存储刚好合适的类型。这节省了装箱/拆箱和类型转换的时间。•在C#中使用as,而不是is。关键字is用来查看引用是否可以被作为某个具体的类型,但是并不返回转换到这个类型的引用。所以,通常当你从is获得一个正的结果时,你首先应该cast——有效地执行两次cast。采用as关键词时,如果可用,则返回cast为新类型的引用;否则返回null。你可以查看null然后做你喜欢做的事情。整体来说,As方法要比is方法快50%。1.5异常处理 异常也是现代语言的典型特征。与传统检查错误码的方式相比,异常是强制性的(不依赖于是否忘记了编写检查错误码的代码)、强类型的、并带有丰富的异常信息(例如调用栈)。 1.5.1不要吃掉异常★ 关于异常处理的最重要原则就是:不要吃掉异常。这个问题与性能无关,但对于编写健壮和易于排错的程序非常重要。这个原则换一种说法,就是不要捕获那些你不能处理的异常。 吃掉异常是极不好的习惯,因为你消除了解决问题的线索。一旦出现错误,定位问题将非常困难。除了这种完全吃掉异常的方式外,只将异常信息写入日志文件但并不做更多处理的做法也同样不妥。 1.5.2不要吃掉异常信息★ 有些代码虽然抛出了异常,但却把异常信息吃掉了。 为异常披露详尽的信息是程序员的职责所在。如果不能在保留原始异常信息含义的前提下附加更丰富和更人性化的内容,那么让原始的异常信息直接展示也要强得多。千万不要吃掉异常。 1.5.3避免不必要的抛出异常 抛出异常和捕获异常属于消耗比较大的操作,在可能的情况下,应通过完善程序逻辑避免抛出不必要不必要的异常。与此相关的一个倾向是利用异常来控制处理逻辑。尽管对于极少数的情况,这可能获得更为优雅的解决方案,但通常而言应该避免。 1.5.4避免不必要的重新抛出异常 如果是为了包装异常的目的(即加入更多信息后包装成新异常),那么是合理的。但是有不少代码,捕获异常没有做任何处理就再次抛出,这将无谓地增加一次捕获异常和抛出异常的消耗,对性能有伤害。1.5.5捕获指定的异常,不要使用通用的System.Exception. //避免 try { } catch(Exceptionexc) { } //推荐 try { } catch(System.NullReferenceExceptionexc) { } catch(System.ArgumentOutOfRangeExceptionexc) { } catch(System.InvalidCastExceptionexc) { }1.5.6要在finally里释放占用的资源使用Try...catch...finally时,要在finally里释放占用的资源如连接,文件流等,不然在Catch到错误后占用的资源不能释放。 try { ... } catch {...} finally { conntion.close() }1.6反射 反射是一项很基础的技术,它将编译期间的静态绑定转换为延迟到运行期间的动态绑定。在很多场景下(特别是类框架的设计),可以获
{
sb.Append(Results[i]);
}
如果连接次数是固定的并且只有几次,此时应该直接用+号连接,保持程序简洁易读。
实际上,编译器已经做了优化,会依据加号次数调用不同参数个数的String.Concat方法。
例如:
Stringstr=str1+str2+str3+str4;
会被编译为String.Concat(str1,str2,str3,str4)。
该方法内部会计算总的String长度,仅分配一次,并不会如通常想象的那样分配三次。
作为一个经验值,当字符串连接操作达到10次以上时,则应该使用StringBuilder。
这里有一个细节应注意:
StringBuilder内部Buffer的缺省值为16,这个值实在太小。
按StringBuilder的使用场景,Buffer肯定得重新分配。
经验值一般用256作为Buffer的初值。
当然,如果能计算出最终生成字符串长度的话,则应该按这个值来设定Buffer的初值。
使用newStringBuilder(256)就将Buffer的初始长度设为了256。
1.2.2避免不必要的调用ToUpper或ToLower方法
String是不变类,调用ToUpper或ToLower方法都会导致创建一个新的字符串。
如果被频繁调用,将导致频繁创建字符串对象。
这违背了前面讲到的“避免频繁创建对象”这一基本原则。
例如,bool.Parse方法本身已经是忽略大小写的,调用时不要调用ToLower方法。
另一个非常普遍的场景是字符串比较。
高效的做法是使用Compare方法,这个方法可以做大小写忽略的比较,并且不会创建新字符串。
例:
conststringC_VALUE="COMPARE";
if(String.Compare(sVariable,C_VALUE,true)==0)
Console.Write("SAME");
还有一种情况是使用HashTable的时候,有时候无法保证传递key的大小写是否符合预期,往往会把key强制转换到大写或小写方法。
实际上HashTable有不同的构造形式,完全支持采用忽略大小写的key:
newHashTable(StringComparer.OrdinalIgnoreCase)。
1.2.3最快的空串比较方法
将String对象的Length属性与0比较是最快的方法:
if(str.Length==0)
其次是与String.Empty常量或空串比较:
if(str==String.Empty)或if(str=="")
注:
C#在编译时会将程序集中声明的所有字符串常量放到保留池中(internpool),相同常量不会重复分配。
1.3多线程
1.3.1线程同步
线程同步是编写多线程程序需要首先考虑问题。
C#为同步提供了Monitor、Mutex、AutoResetEvent和ManualResetEvent对象来分别包装Win32的临界区、互斥对象和事件对象这几种基础的同步机制。
C#还提供了一个lock语句,方便使用,编译器会自动生成适当的Monitor.Enter和Monitor.Exit调用。
1.3.1.1同步粒度
同步粒度可以是整个方法,也可以是方法中某一段代码。
为方法指定MethodImplOptions.Synchronized属性将标记对整个方法同步。
[MethodImpl(MethodImplOptions.Synchronized)]
publicstaticSerialManagerGetInstance()
if(instance==null)
instance=newSerialManager();
returninstance;
通常情况下,应减小同步的范围,使系统获得更好的性能。
简单将整个方法标记为同步不是一个好主意,除非能确定方法中的每个代码都需要受同步保护。
1.3.1.2同步策略
使用lock进行同步,同步对象可以选择Type、this或为同步目的专门构造的成员变量。
避免锁定Type★
锁定Type对象会影响同一进程中所有AppDomain该类型的所有实例,这不仅可能导致严重的性能问题,还可能导致一些无法预期的行为。
这是一个很不好的习惯。
即便对于一个只包含static方法的类型,也应额外构造一个static的成员变量,让此成员变量作为锁定对象。
避免锁定this
锁定this会影响该实例的所有方法。
假设对象obj有A和B两个方法,其中A方法使用lock(this)对方法中的某段代码设置同步保护。
现在,因为某种原因,B方法也开始使用lock(this)来设置同步保护了,并且可能为了完全不同的目的。
这样,A方法就被干扰了,其行为可能无法预知。
所以,作为一种良好的习惯,建议避免使用lock(this)这种方式。
使用为同步目的专门构造的成员变量
这是推荐的做法。
方式就是new一个object对象,该对象仅仅用于同步目的。
如果有多个方法都需要同步,并且有不同的目的,那么就可以为些分别建立几个同步成员变量。
1.3.1.4集合同步
C#为各种集合类型提供了两种方便的同步机制:
Synchronized包装器和SyncRoot属性。
//CreatesandinitializesanewArrayList
ArrayListmyAL=newArrayList();
myAL.Add("The");
myAL.Add("quick");
myAL.Add("brown");
myAL.Add("fox");
//CreatesasynchronizedwrapperaroundtheArrayList
ArrayListmySyncdAL=ArrayList.Synchronized(myAL);
调用Synchronized方法会返回一个可保证所有操作都是线程安全的相同集合对象。
考虑mySyncdAL[0]=mySyncdAL[0]+"test"这一语句,读和写一共要用到两个锁。
一般讲,效率不高。
推荐使用SyncRoot属性,可以做比较精细的控制。
1.3.2使用ThreadStatic替代NameDataSlot★
存取NameDataSlot的Thread.GetData和Thread.SetData方法需要线程同步,涉及两个锁:
一个是LocalDataStore.SetData方法需要在AppDomain一级加锁,另一个是ThreadNative.GetDomainLocalStore方法需要在Process一级加锁。
如果一些底层的基础服务使用了NameDataSlot,将导致系统出现严重的伸缩性问题。
规避这个问题的方法是使用ThreadStatic变量。
示例如下:
publicsealedclassInvokeContext
[ThreadStatic]
privatestaticInvokeContextcurrent;
privateHashtablemaps=newHashtable();
1.3.3多线程编程技巧
1.3.3.1使用DoubleCheck技术创建对象
internalIDictionaryKeyTable
get
if(this._keyTable==null)
lock(base._lock)
this._keyTable=newHashtable();
returnthis._keyTable;
创建单例对象是很常见的一种编程情况。
一般在lock语句后就会直接创建对象了,但这不够安全。
因为在lock锁定对象之前,可能已经有多个线程进入到了第一个if语句中。
如果不加第二个if语句,则单例对象会被重复创建,新的实例替代掉旧的实例。
如果单例对象中已有数据不允许被破坏或者别的什么原因,则应考虑使用DoubleCheck技术。
1.4类型系统
1.4.1避免无意义的变量初始化动作
CLR保证所有对象在访问前已初始化,其做法是将分配的内存清零。
因此,不需要将变量重新初始化为0、false或null。
需要注意的是:
方法中的局部变量不是从堆而是从栈上分配,所以C#不会做清零工作。
如果使用了未赋值的局部变量,编译期间即会报警。
不要因为有这个印象而对所有类的成员变量也做赋值动作,两者的机理完全不同!
1.4.2ValueType和ReferenceType
1.4.2.1以引用方式传递值类型参数
值类型从调用栈分配,引用类型从托管堆分配。
当值类型用作方法参数时,默认会进行参数值复制,这抵消了值类型分配效率上的优势。
作为一项基本技巧,以引用方式传递值类型参数可以提高性能。
1.4.2.2为ValueType提供Equals方法
.net默认实现的ValueType.Equals方法使用了反射技术,依靠反射来获得所有成员变量值做比较,这个效率极低。
如果我们编写的值对象其Equals方法要被用到(例如将值对象放到HashTable中),那么就应该重载Equals方法。
publicstructRectangle
publicdoubleLength;
publicdoubleBreadth;
publicoverrideboolEquals(objectob)
if(obisRectangle)
returnEquels((Rectangle)ob))
else
returnfalse;
privateboolEquals(Rectanglerect)
returnthis.Length==rect.Length&&this.Breadth==rect.Breach;
1.4.2.3避免装箱和拆箱
C#可以在值类型和引用类型之间自动转换,方法是装箱和拆箱。
装箱需要从堆上分配对象并拷贝值,有一定性能消耗。
如果这一过程发生在循环中或是作为底层方法被频繁调用,则应该警惕累计的效应。
一种经常的情形出现在使用集合类型时。
ArrayListal=newArrayList();
for(inti=0;i<1000;i++)
al.Add(i);//ImplicitlyboxedbecauseAdd()takesanobject
intf=(int)al[0];//Theelementisunboxed
但是得当心!
如果你像使用引用类型那么频繁的使用一个值类型的话,值类型的优势会很快被耗尽。
比如,把一个值类型压到一个含有对象类型的群集。
这叫做装箱,很耗用处理器周期,尤其是当你的代码在把它作为值(对它进行数学运算)和把它作为引用之间来回运行时。
1.4.3尽可能使用最合适的类型
•尽可能使用最合适的类型来描述数据,从而减少类型转换。
•使用泛型来创建群集和其它的数据结构,这样,在运行时,它们就可以被实例化来存储刚好合适的类型。
这节省了装箱/拆箱和类型转换的时间。
•在C#中使用as,而不是is。
关键字is用来查看引用是否可以被作为某个具体的类型,但是并不返回转换到这个类型的引用。
所以,通常当你从is获得一个正的结果时,你首先应该cast——有效地执行两次cast。
采用as关键词时,如果可用,则返回cast为新类型的引用;否则返回null。
你可以查看null然后做你喜欢做的事情。
整体来说,As方法要比is方法快50%。
1.5异常处理
异常也是现代语言的典型特征。
与传统检查错误码的方式相比,异常是强制性的(不依赖于是否忘记了编写检查错误码的代码)、强类型的、并带有丰富的异常信息(例如调用栈)。
1.5.1不要吃掉异常★
关于异常处理的最重要原则就是:
不要吃掉异常。
这个问题与性能无关,但对于编写健壮和易于排错的程序非常重要。
这个原则换一种说法,就是不要捕获那些你不能处理的异常。
吃掉异常是极不好的习惯,因为你消除了解决问题的线索。
一旦出现错误,定位问题将非常困难。
除了这种完全吃掉异常的方式外,只将异常信息写入日志文件但并不做更多处理的做法也同样不妥。
1.5.2不要吃掉异常信息★
有些代码虽然抛出了异常,但却把异常信息吃掉了。
为异常披露详尽的信息是程序员的职责所在。
如果不能在保留原始异常信息含义的前提下附加更丰富和更人性化的内容,那么让原始的异常信息直接展示也要强得多。
千万不要吃掉异常。
1.5.3避免不必要的抛出异常
抛出异常和捕获异常属于消耗比较大的操作,在可能的情况下,应通过完善程序逻辑避免抛出不必要不必要的异常。
与此相关的一个倾向是利用异常来控制处理逻辑。
尽管对于极少数的情况,这可能获得更为优雅的解决方案,但通常而言应该避免。
1.5.4避免不必要的重新抛出异常
如果是为了包装异常的目的(即加入更多信息后包装成新异常),那么是合理的。
但是有不少代码,捕获异常没有做任何处理就再次抛出,这将无谓地增加一次捕获异常和抛出异常的消耗,对性能有伤害。
1.5.5捕获指定的异常,不要使用通用的System.Exception.
//避免
try
catch(Exceptionexc)
//推荐
catch(System.NullReferenceExceptionexc)
catch(System.ArgumentOutOfRangeExceptionexc)
catch(System.InvalidCastExceptionexc)
1.5.6要在finally里释放占用的资源
使用Try...catch...finally时,要在finally里释放占用的资源如连接,文件流等,不然在Catch到错误后占用的资源不能释放。
...
catch
{...}
finally
conntion.close()
1.6反射
反射是一项很基础的技术,它将编译期间的静态绑定转换为延迟到运行期间的动态绑定。
在很多场景下(特别是类框架的设计),可以获
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1