CNET性能优化总结_第1页
CNET性能优化总结_第2页
CNET性能优化总结_第3页
CNET性能优化总结_第4页
CNET性能优化总结_第5页
已阅读5页,还剩11页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

C#.NET性能优化总结垃圾回收垃圾回收解放了手工管理对象的工作,提高了程序的健壮性,但副作用就是程序代码可能对于对象创建变得随意。避免不必要的对象创建由于垃圾回收的代价较高,所以C#程序开发要遵循的一个基本原则就是避免不必要的对象创建。以下列举一些常见的情形。避免循环创建对象。如果对象并不会随每次循环而改变状态,那么在循环中反复创建对象将带来性能损耗。高效的做法是将对象提到循环外面创建。在需要逻辑分支中创建对象。如果对象只在某些逻辑分支中才被用到,那么应只在该逻辑分支中创建对象。使用常量避免创建对象。程序中不应出现如newDecimal(0)之类的代码,这会导致小对象频繁创建及回收,正确的做法是使用Decimal.Zero常量。我们有设计自己的类时,也可以学习这个设计手法,应用到类似的场景中。使用StringBuilder做字符串连接。不要使用空析构函数如果类包含析构函数,由创建对象时会在Finalize队列中添加对象的引用,以保证当对象无法可达时,仍然可以调用到Finalize方法。垃圾回收器在运行期间,会启动一个低优先级的线程处理该队列。相比之下,没有析构函数的对象就没有这些消耗。如果析构函数为空,这个消耗就毫无意义,只会导致性能降低。因此,不要使用空的析构函数。在实际情况中,许多曾在析构函数中包含处理代码,但后来因为种种原因被注释掉或者删除掉了,只留下一个空壳,此时应注意把析构函数本身注释掉或删除掉。实现Idisposable接口垃圾回收事实上只支持托管内在的回收,对于其他的非托管资源,例如WindowGDI句柄或数据库连接,在析构函数中释放这些资源有很大问题。原因是垃圾回收依赖于内存在紧张的情况,虽然数据库连接可能已濒临耗尽,但如果内存还很充足的话,垃圾回收是不会运行的。C#的IDisposable接口是一种显式释放资源的机制。通过提供using语句,还简化了使用方式(编译器自动生成try...finally块,并在finally块中调用Dispose方法)。对于申请非托管资源对象,应为其实现IDisposable接口,以保证资源一旦超出using语句范围,即得到及时释放。这对于构造健壮且性能优良的程序非常有意义。为防止对象的Dispose方法不被调用的情况发生,一般还要提供析构函数,两者调用一个处理资源释放的公共方法。同时,Dispose方法应调用System.GC.SuppressFinalize(this),告诉垃圾回收器无需再处理Finalize方法了。String操作使用StringBuilder做字符串连接String是不变类,使用+操作连接字符串将会导致创建一个新的字符串。如果字符串连接次数不是固定的,例如在一个循环中,则应该使用StringBuilder类来做字符串连接工作。因为StringBuilder内部有一个StringBuffer,连接操作不会每次分配新的字符串空间。只有当连接后的字符串超出Buffer大小时,才会申请新的Buffer空间。典型代码如下:StringBuildersb=newStringBuilder(256);for(inti=0;i<Results.Count;i++){sb.Append(Results[i]);}如果连接次数是固定的并且只有几次,此时应该直接用+号连接,保持程序简洁易读。实际上,编译器已经做了优化,会依据加号次数调用不同参数个数的String.Concat方法。例如:Stringstr=str1+str2+str3+str4;会被编译为String.Concat(str1,str2,str3,str4)。该方法内部会计算总的String长度,仅分配一次,并不会如通常想象的那样分配三次。作为一个经验值,当字符串连接操作达到10次以上时,则应该使用StringBuilder。这里有一个细节应注意:StringBuilder内部Buffe的缺省值为16,这个值实在太小。按StringBuilder的使用场景,Buffer肯定得重新分配。经验值一般用256作为Buffer的初值。当然,如果能计算出最终生成字符串长度的话,则应该按这个值来设定Buffer的初值。使用newStringBuilder(256)就将Buffer的初始长度设为了256。避免不必要的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)。最快的空串比较方法将String对象的Length属性与0比较是最快的方法:if(str.Length==0)其次是与String.Empty常量或空串比较:if(str==String.Empty)或if(str=="")注:C#在编译时会将程序集中声明的所有字符串常量放到保留池中(internpool),相同常量不会重复分配。多线程线程同步线程同步是编写多线程程序需要首先考虑问题。C#为同步提供了Monitor、Mutex、AutoResetEvent和ManualResetEvent对象来分别包装Win32的临界区、互斥对象和事件对象这几种基础的同步机制。C#还提供了一个lock语句,方便使用,编译器会自动生成适当的Monitor.Enter和Monitor.Exit调用。同步粒度同步粒度可以是整个方法,也可以是方法中某一段代码。为方法指定MethodImplOptions.Synchronized属性将标记对整个方法同步。例如:[MethodImpl(MethodImplOptions.Synchronized)]publicstaticSerialManagerGetInstance(){if(instance==null){ instance=newSerialManager(); }returninstance;}通常情况下,应减小同步的范围,使系统获得更好的性能。简单将整个方法标记为同步不是一个好主意,除非能确定方法中的每个代码都需要受同步保护。同步策略使用lock进行同步,同步对象可以选择Type、this或为同步目的专门构造的成员变量。避免锁定Type★锁定Type对象会影响同一进程中所有AppDomain该类型的所有实例,这不仅可能导致严重的性能问题,还可能导致一些无法预期的行为。这是一个很不好的习惯。即便对于一个只包含static方法的类型,也应额外构造一个static的成员变量,让此成员变量作为锁定对象。避免锁定this锁定this会影响该实例的所有方法。假设对象obj有A和B两个方法,其中A方法使用lock(this)对方法中的某段代码设置同步保护。现在,因为某种原因,B方法也开始使用lock(this)来设置同步保护了,并且可能为了完全不同的目的。这样,A方法就被干扰了,其行为可能无法预知。所以,作为一种良好的习惯,建议避免使用lock(this)这种方式。使用为同步目的专门构造的成员变量这是推荐的做法。方式就是new一个object对象,该对象仅仅用于同步目的。如果有多个方法都需要同步,并且有不同的目的,那么就可以为些分别建立几个同步成员变量。集合同步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属性,可以做比较精细的控制。使用ThreadStatic替代NameDataSlot★存取NameDataSlot的Thread.GetData和Thread.SetData方法需要线程同步,涉及两个锁:一个是LocalDataStore.SetData方法需要在AppDomain一级加锁,另一个是ThreadNative.GetDomainLocalStore方法需要在Process一级加锁。如果一些底层的基础服务使用了NameDataSlot,将导致系统出现严重的伸缩性问题。规避这个问题的方法是使用ThreadStatic变量。示例如下:publicsealedclassInvokeContext{[ThreadStatic]privatestaticInvokeContextcurrent;privateHashtablemaps=newHashtable();}多线程编程技巧使用DoubleCheck技术创建对象internalIDictionaryKeyTable{get{if(this._keyTable==null){lock(base._lock){if(this._keyTable==null){ this._keyTable=newHashtable(); }}}returnthis._keyTable;}}创建单例对象是很常见的一种编程情况。一般在lock语句后就会直接创建对象了,但这不够安全。因为在lock锁定对象之前,可能已经有多个线程进入到了第一个if语句中。如果不加第二个if语句,则单例对象会被重复创建,新的实例替代掉旧的实例。如果单例对象中已有数据不允许被破坏或者别的什么原因,则应考虑使用DoubleCheck技术。类型系统避免无意义的变量初始化CLR保证所有对象在访问前已初始化,其做法是将分配的内存清零。因此,不需要将变量重新初始化为0、false或null。需要注意的是:方法中的局部变量不是从堆而是从栈上分配,所以C#不会做清零工作。如果使用了未赋值的局部变量,编译期间即会报警。不要因为有这个印象而对所有类的成员变量也做赋值动作,两者的机理完全不同。ValueType和ReferenceType以引用方式传递值类型参数值类型从调用栈分配,引用类型从托管堆分配。当值类型用作方法参数时,默认会进行参数值复制,这抵消了值类型分配效率上的优势。作为一项基本技巧,以引用方式传递值类型参数可以提高性能。为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; }}避免装箱和拆箱C#可以在值类型和引用类型之间自动转换,方法是装箱和拆箱。装箱需要从堆上分配对象并拷贝值,有一定性能消耗。如果这一过程发生在循环中或是作为底层方法被频繁调用,则应该警惕累计的效应。一种经常的情形出现在使用集合类型时。例如://避免如下操作ArrayListal=newArrayList();for(inti=0;i<1000;i++){ //装箱操作al.Add(i);}//拆箱操作intf=(int)al[0];但是得当心,如果你像使用引用类型那么频繁的使用一个值类型的话,值类型的优势会很快被耗尽。比如,把一个值类型压到一个含有对象类型的群集。这叫做装箱,很耗用处理器周期,尤其是当你的代码在把它作为值(对它进行数学运算)和把它作为引用之间来回运行时。尽可能使用最合适的类型尽可能使用最合适的类型来描述数据,从而减少类型转换。使用泛型来创建群集和其它的数据结构,这样,在运行时,它们就可以被实例化来存储刚好合适的类型。这节省了装箱/拆箱和类型转换的时间。在C#中使用as,而不是is。关键字is用来查看引用是否可以被作为某个具体的类型,但是并不返回转换到这个类型的引用。所以,通常当你从is获得一个正的结果时,你首先应该cast——有效地执行两次cast。采用as关键词时,如果可用,则返回cast为新类型的引用;否则返回null。你可以查看null然后做你喜欢做的事情。整体来说,as方法要比is方法快50%。异常处理不要吃掉异常关于异常处理的最重要原则就是:不要吃掉异常。这个问题与性能无关,但对于编写健壮和易于排错的程序非常重要。这个原则换一种说法,就是不要捕获那些你不能处理的异常。吃掉异常是极不好的习惯,因为你消除了解决问题的线索。一旦出现错误,定位问题将非常困难。除了这种完全吃掉异常的方式外,只将异常信息写入日志文件但并不做更多处理的做法也同样不妥。不要吃掉异常信息有些代码虽然抛出了异常,但却把异常信息吃掉了。为异常披露详尽的信息是程序员的职责所在。如果不能在保留原始异常信息含义的前提下附加更丰富和更人性化的内容,那么让原始的异常信息直接展示也要强得多。千万不要吃掉异常信息。避免不必要的抛出异常抛出异常和捕获异常属于消耗比较大的操作,在可能的情况下,应通过完善程序逻辑避免抛出不必要的异常。与此相关的一个倾向是利用异常来控制处理逻辑。尽管对于极少数的情况,这可能获得更为优雅的解决方案,但通常而言应该避免。避免不必要的重新抛出异常如果是为了包装异常的目的(即加入更多信息后包装成新异常),那么是合理的。但是有不少代码,捕获异常没有做任何处理就再次抛出,这将无谓地增加一次捕获异常和抛出异常的消耗,对性能有伤害。捕获指定的异常不要使用通用的System.Exception//避免try{}catch(Exceptionexc){}//推荐try{}catch(System.NullReferenceExceptionexc){}catch(System.ArgumentOutOfRangeExceptionexc){}catch(System.InvalidCastExceptionexc){}要在finally里释放占用的资源使用Try...catch...finally时,要在finally里释放占用的资源如:连接,文件流等,不然在Catch到错误后占用的资源不能释放。

反射反射是一项很基础的技术,它将编译期间的静态绑定转换为延迟到运行期间的动态绑定。在很多场景下(特别是类框架的设计),可以获得灵活易于扩展的架构。但带来的问题是与静态绑定相比,动态绑定会对性能造成较大的伤害。反射分类typecomparison:类型判断,主要包括is和typeof两个操作符及对象实例上的GetType调用。这是最轻型的消耗,可以无需考虑优化问题。注意typeof运算符比对象实例上的GetType方法要快,只要可能则优先使用typeof运算符。memberenumeration:成员枚举,用于访问反射相关的元数据信息,例如Assembly.GetModule、Module.GetType、Type对象上的IsInterface、IsPublic、GetMethod、GetMethods、GetProperty、GetProperties、GetConstructor调用等。尽管元数据都会被CLR缓存,但部分方法的调用消耗仍非常大,不过这类方法调用频度不会很高,所以总体看性能损失程度中等。memberinvocation:成员调用,包括动态创建对象及动态调用对象方法,主要有Activator.CreateInstance、Type.InvokeMember等。动态创建对象C#主要支持5种动态创建对象的方式:1.Type.InvokeMember2.ContructorInfo.Invoke3.Activator.CreateInstance(Type)4.Activator.CreateInstance(assemblyName,typeName)5.Assembly.CreateInstance(typeName)最快的是方式3,与DirectCreate的差异在一个数量级之内,约慢7倍的水平。其他方式,至少在40倍以上,最慢的是方式4,要慢三个数量级。动态方法调用方法调用分为编译期的早期绑定和运行期的动态绑定两种,称为Early-BoundInvocation和Late-BoundInvocation。Early-BoundInvocation可细分为Direct-call、Interface-call和Delegate-call。Late-BoundInvocation主要有Type.InvokeMember和MethodBase.Invoke,还可以通过使用LCG(LightweightCodeGeneration)技术生成IL代码来实现动态调用。从测试结果看,相比DirectCall,Type.InvokeMember要接近慢三个数量级;MethodBase.Invoke虽然比Type.InvokeMember要快三倍,但比DirectCall仍慢270倍左右。可见动态方法调用的性能是非常低下的。我们的建议是:除非要满足特定的需求,否则不要使用。推荐的使用原则1.如果可能,则避免使用反射和动态绑定2.使用接口调用方式将动态绑定改造为早期绑定3.使用Activator.CreateInstance(Type)方式动态创建对象4.使用typeof操作符代替GetType调用5.在已获得Type的情况下,使用Assembly.CreateInstance(type.FullName)。基本代码技巧这里描述一些应用场景下,可以提高性能的基本代码技巧。对处于关键路径的代码,进行这类的优化还是很有意义的。普通代码可以不做要求,但养成一种好的习惯也是有意义的。循环写法可以把循环的判断条件用局部变量记录下来。局部变量往往被编译器优化为直接使用寄存器,相对于普通从堆或栈中分配的变量速度快。如果访问的是复杂计算属性的话,提升效果将更明显。for(inti=0,j=collection.GetIndexOf(item);i<j;i++)需要说明的是:这种写法对于CLR集合类的Count属性没有意义,原因是编译器已经按这种方式做了特别的优化。拼装字符串拼装好之后再删除是很低效的写法。有些方法其循环长度在大部分情况下为1,这种写法的低效就更为明显了://避免publicstaticstringToString(MetadataKeyentityKey){stringstr="";object[]vals=entityKey.values;for(inti=0;i<vals.Length;i++){str+=","+vals[i].ToString();}returnstr==""?"":str.Remove(0,1);}推荐下面的写法://推荐if(str.Length==0)str=vals[i].ToString();elsestr+=","+vals[i].ToString();其实这种写法非常自然,而且效率很高,完全不需要用个Remove方法绕来绕去。避免两次检索集合元素获取集合元素时,有时需要检查元素是否存在。通常的做法是先调用ContainsKey(或Contains)方法,然后再获取集合元素。这种写法非常符合逻辑。但如果考虑效率,可以先直接获取对象,然后判断对象是否为null来确定元素是否存在。对于Hashtable,这可以节省一次GetHashCode调用和n次Equals比较。如下面的示例://避免publicIDataGetItemByID(Guidid){IDatadata1=null;if(this.idTable.ContainsKey(id.ToString()){data1=this.idTable[id.ToString()]asIData;}returndata1;}其实完全可用一行代码完成:returnthis.idTable[id]asIData;避免两次类型转换考虑如下示例,其中包含了两处类型转换://避免if(objisSomeType){SomeTypest=(SomeType)obj;st.SomeTypeMethod();}效率更高的做法如下://推荐SomeTypest=objasSomeType;if(st!=null){st.SomeTypeMethod();}为字符串容器声明常量为字符串容器申明变量,不要直接把字符封装在双引号“”里面。//避免MyObjectobj=newMyObject();obj.Status="ACTIVE";//推荐conststringC_STATUS="ACTIVE";MyObjectobj=newMyObject();obj.Status=C_STATUS;使用StringBuilder用StringBuilder代替使用字符串连接符“+”//避免StringsXML="";sXML+="";sXML+="Data";sXML+="";sXML+="";//推荐StringBuildersbXML=newStringBuilder();sbXML.Append("");sbXML.Append("");sbXML.Append("Data");sbXML.Append("");sbXML.Append("");避免在循环体里声明变量应该在循环体外声明变量,在循环体里初始化。//避免for(inti=0;i<10;i++){SomeClassobjSC=newSomeClass();}//推荐SomeClassobjSC=null;for(inti=0;i<10;i++){objSC=newSomeClass();)HashtableHashtable机制Hashtable是一种使用非常频繁的基础集合类型。需要理解影响Hashtable的效率有两个因素:一是散列码(GetHashCode方法),二是等值比较(Equals方法)。Hashtable首先使用键的散列码将对象分布到不同的存储桶中,随后在该特定的存储桶中使用键的Equals方法进行查找。良好的散列码是第一位的因素,最理想的情况是每个不同的键都有不同的散列码。Equals方法也很重要,因为散列只需要做一次,而存储桶中查找键可能需要做多次。从实际经验看,使用Hashtable时,Equals方法的消耗一般会占到一半以上。System.Object类提供了默认的GetHashCode实现,使用对象在内存中的地址作为散列码。我们遇到过一个用Hashtable来缓存对象的例子,每次根据传递的OQL表达式构造出一个ExpressionList对象,再调用QueryCompiler的方法编译得到CompiledQuery对象。以ExpressionList对象和CompiledQuery对象作为键值对存储到Hashtable中。ExpressionList对象没有重载GetHashCode实现,其超类ArrayList也没有,这样最后用的就是System.Object类的GetHashCode实现。由于ExpressionList对象会每次构造,因此它的HashCode每次都不同,所以这个CompiledQueryCache根本就没有起到预想的作用。这个小小的疏漏带来了重大的性能问题,由于解析OQL表达式频繁发生,导致CompiledQueryCache不断增长,造成服务器内存泄漏。解决这个问题的最简单方法就是提供一个常量实现,例如让散列码为常量0。虽然这会导致所有对象汇聚到同一个存储桶中,效率不高,但至少可以解决掉内存泄漏问题。当然,最终还是会实现一个高效的GetHashCode方法的。以上介绍这些Hashtable机理,主要是希望大家理解:如果使用Hashtable,你应该检查一下对象是否提供了适当的GetHashCode和Equals方法实现。否则,有可能出现效率不高或者与预期行为不符的情况。使用HashTale代替其他字典集合类型的情形其他字典集合类型(如StringDictionary,NameValueCollection,HybridCollection),存放少量数据的时候可以使用HashTable。很多非泛型集合类都有对应的泛型集合类,下面是常用的非泛型集合类以及对应的泛型集合类:非泛型集合类泛型集合类ArrayListList<T>HashTableDIctionary<T>QueueQueue<T>StackStack<T>SortedListSortedList<T>我们用的比较多的非泛型集合类主要有ArrayList类和HashTable类。我们经常用HashTable来存储将要写入到数据库或者返回的信息,在这之间要不断的进行类型的转化,增加了系统装箱和拆箱的负担,如果我们操纵的数据类型相对确定的化用Dictionary<TKey,TValue>集合类来存储数据就方便多了,例如我们需要在电子商务网站中存储用户的购物车信息(商品名,对应的商品个数)时,完全可以用Dictionary<string,int>来存储购物车信息,而不需要任何的类型转化。避免使用ArrayList因为任何对象添加到ArrayList都要封箱为System.Object类型,从ArrayList取出数据时,要拆箱回实际的类型。建议使用自定义的集合类型代替ArrayList。.NET2.0提供了一个新的类型,叫泛型,这是一个强类型,使用泛型集合就可以避免了封箱和拆箱的发生,提高了性能。从XML对象读取数据如果只是从XML对象读取数据,用只读的XPathDocument代替XMLDocument,可以提高性能。//避免XmlDocumentxmld=newXmlDocument();xmld.LoadXml(sXML);txtName.Text=xmld.SelectSingleNode("/packet/child").InnerText;//推荐XpathDocumentxmldContext=newXPathDocument(newStringReader(oContext.Value));XPathNavigatorxnav=xmldContext.CreateNavigator();XPathNodeIteratorxpNodeIter=xnav.Select("packet/child");iCount=xpNodeIter.Count;xpNodeIter=xnav.SelectDescendants(XPathNodeType.Element,false);while(xpNodeIter.MoveNext()){sCurrValues+=xpNodeIter.Current.Value+"~";}Ado.Net应用A的一些思考原则1.根据数据使用的方式来设计数据访问层2.缓存数据,避免不必要的操作3.使用服务帐户进行连接4.必要时申请,尽早释放5.关闭可关闭的资源6.减少往返7.仅返回需要的数据8.选择适当的事务类型9.使用存储过程Connection数据库连接是一种共享资源,并且打开和关闭的开销较大。A默认启用了连接池机制,关闭连接不会真的关闭物理连接,而只是把连接放回到连接池中。因为池中共享的连接资源始终是有限的,如果在使用连接后不尽快关闭连接,那么就有可能导致申请连接的线程被阻塞住,影响整个系统的性能表现。在方法中打开和关闭连接这个原则有几层含义:1.主要目的是为了做到必要时申请和尽早释放2.不要在类的构造函数中打开连接、在析构函数中释放连接。因为这将依赖于垃圾回收,而垃圾回收只受内存影响,回收时机不定3.不要在方法之间传递连接,这往往导致连接保持打开的时间过长这里强调一下在方法之间传递连接的危害:曾经在压力测试中遇到过一个测试案例,当增大用户数的时候,这个案例要比别的案例早很久就用掉连接池中的所有连接。经分析,就是因为A方法把一个打开的连接传递到了B方法,而B方法又调用了一个自行打开和关闭连接的C方法。在A方法的整个运行期间,它至少需要占用两条连接才能够成功工作,并且其中的一条连接占用时间还特别长,所以造成连接池资源紧张,影响了整个系统的可伸缩性!显式关闭连接Connection对象本身在垃圾回收时可以被关闭,而依赖垃圾回收是很不好的策略。推荐使用using语句显式关闭连接,如下例:using(SqlConnectionconn=newSqlConnection(connString)){ conn.Open(); }//Disposeisautomaticallycalledontheconnvariablehere确保连接池启用A是为每个不同的连接串建立连接池,因此应该确保连接串不会出现与具体用户相关的信息。另外,要注意连接串是大小写敏感的。不要缓存连接例如,把连接缓存到Session或Application中。在启用连接池的情况下,这种做法没有任何意义。Command使用ExecuteScalar和ExecuteNonQuery如果想返回像Count(*)、Sum(Price)或Avg(Quantity)那样的单值,可以使用ExecuteScalar方法。ExecuteScalar返回第一行第一列的值,将结果集作为标量值返回。因为单独一步就能完成,所以ExecuteScalar不仅简化了代码,还提高了性能。使用不返回行的SQL语句时,例如修改数据(INSERT、UPDATE或DELETE)或仅返回输出参数或返回值,请使用ExecuteNonQuery。这避免了用于创建空DataReader的任何不必要处理。使用Prepare当需要重复执行同一SQL语句多次,可考虑使用Prepare方法提升效率。需要注意的是,如果只是执行一次或两次,则完全没有必要。例如:cmd.CommandText="insertintoTable1(Col1,Col2)values(@val1,@val2)";cmd.Parameters.Add("@val1",SqlDbType.Int,4,"Col1");cms.Parameters.Add("@val2",SqlDbType.NChar,50,"Col2");cmd.Parameters[0].Value=1;cmd.Parameters[1].Value="XXX";cmd.Prepare();cmd.ExecuteNonQuery();cmd.Parameters[0].Value=2;cmd.Parameters[1].Value="YYY";cmd.ExecuteNonQuery();cmd.Parameters[0].Value=3;cmd.Parameters[1].Value="ZZZ";cmd.ExecuteNonQuery();使用绑定变量★SQL语句需要先被编译成执行计划,然后再执行。如果使用绑定变量的方式,那么这个执行计划就可以被后续执行的SQL语句所复用。而如果直接把参数合并到了SQL语句中,由于参数值千变万化,执行计划就难以被复用了。例如上面Prepare一节给出的示例,如果把参数值直接写到insert语句中,那么上面的四次调用将需要编译四次执行计划。为避免这种情况造成性能损失,要求一律使用绑定变量方式。DataReaderDataReader最适合于访问只读的单向数据集。与DataSet不同,数据集并不全部在内存中,而是随不断发出的read请求,一旦发现数据缓冲区中的数据均被读取,则从数据源传输一个数据缓冲区大小的数据块过来。另外,DataReader保持连接,DataSet则与连接断开。2.4.1显式关闭DataReader与连接类似,也需要显式关闭DataReader。另外,如果与DataReader关联的Connection仅为DataReader服务的话,可考虑使用Command对象的ExecuteReader(CommandBehavior.CloseConnection)方式。这可以保证当DataReader关闭时,同时自动关闭Connection。用索引号访问代替名称索引号访问属性从Row中访问某列属性,使用索引号的方式比使用名称方式有细微提高。如果会被频繁调用,例如在循环中,那么可考虑此类优化。示例如下:cmd.CommandText="selectCol1,Col2fromTable1";SqlDataReaderdr=cmd.ExecuteReader();intcol1=dr.GetOrdinal("Col1");intcol2=dr.GetOrdinal("Col2");while(dr.Read()){Console.WriteLine(dr[col1]+"_"+dr[col2]); }使用类型化方法访问属性从Row中访问某列属性,用GetString、GetInt32这种显式指明类型的方法,其效率较通用的GetValue方法有细微提高,因为不需要做类型转换。使用多数据集部分场景可以考虑一次返回多数据集来降低网络交互次数,提升效率。示例如下:cmd.CommandText="StoredProcedureName";//Thestoredprocedurereturnsmultipleresultsets.SqlDataReaderdr=cmd.ExecuteReader();while(dr.read())//readfirstresultsetdr.NextResult();while(dr.read())//DataSet利用索引加快查找行的效率如果需要反复查找行,建议增加索引。有两种方式:1.设置DataTable的PrimaryKey适用于按PrimaryKey查找行的情况。注意此时应调用DataTable.Rows.Find方法,一般惯用的Select方法不能利用索引。2.使用DataView适用于按Non-PrimaryKey查找行的情况。可为DataTable创建一个DataView,并通过SortOrder参数指示建立索引。此后使用Find或FindRows查找行。在C++中封装的概念是把一个对象的外观接口同实际工作方式(实现)分离开来,但是C++的封装是不完全的,编译器必须知道一个对象的所有部分的声明,以便创建和管理它。我们可以想象一种只需声明一个对象的公共接口部分的编程语言,而将私有的实现部分隐藏起来。C++在编译期间要尽可能多地做静态类型检查。这意味着尽早捕获错误,也意味着程序具有更高的效率。然而这对私有的实现部分来说带来两个影响:一是即使程序员不能轻易地访问实现部分,但他可以看到它;二是造成一些不必要的重复编译。然而C++并没有将这个原则应用到二进制层次上,这是因为C++的类既是描述了一个接口同时也描述了实现的过程,示例如下:classCMyString{private:constintm_cch;char*m_psz;public:CMyString(constchar*psz);~CMyString();intLength()const;intIndex(constchar*psz)const;}CMyStirng对外过多的暴露了内存布局实现的细节,这些信息过度的依赖于这些成员变量的大小和顺序,从而导致了客户过度依赖于可执行代码之间的二进制耦合关系,这样的接口不利于跨语言跨平台的软件开发和移植。Handle-Body模式解决这个问题的技术有一种叫句柄类(handleclasses)。有关实现的任何东西都消失了,只剩一个单一的指针“m_pThis”。该指针指向一个结构,该结构的定义与其所有的成员函数的定义都出现在实现文件中。这样,只要接口部分不改变,头文件就不需变动。而实现部分可以按需要任意更动,完成后只要对实现文件进行重新编译,然后再连接到项目中。下面是这项技术的简单例子。头文件中只包含公共的接口和一个简单的没有完全指定的类指针。classCMyStringHandle{private:classCMyString;CMyString*m_pThis;public:CMyStringHandle(constchar*psz);~CMyStringHandle();intLength()const;intIndex(constchar*psz)const;};CMyStringHandle::CMyStringHandle(constchar*psz):m_pThis(newCMyString(psz));{ }CMyStringHandle::~CMyStringHandle(){ deletem_pThis; }intCMyStringHandle::Length(){ returnm_pThis->Length(); }intCMyStringHandle::Index(constchar*psz){ returnm_pThis->Index(psz); }这是所有客户程序员都能看到的。classCMyString; 是一个没有完全指定的类型说明或类声明(一个类的定义包含类的主体)。它告诉编译器,CMyString是一个结构的名字,但没有提供有关该结构的任何东西。这对产生一个指向结构的指针来说已经足够了。但我们在提供一个结构的主体部分之前不能创建一个对象。在这种技术里,包含具体实现的结构主体被隐藏在实现文件中。在设计模式中,这就叫做Handle-Body模式,Handle-Body只含有一个实体指针,服务的数据成员永远被封闭在服务系统中。Handle-Body的布局结构永远不会随着实现类数据成员的加入或者删除或者修改而导致Handle-Body的修改,即Handle-Body协议不依赖于C++实现类的任何细节。这就有效的对用户的编译器隐藏了这些细节,用户在使用对这项技术时候,Handle-Body接口成了它唯一的入口。然而Handle-Body模式也有自己的弱点:1、接口类必须把每一个方法调用显示的传递给实现类,这在一个只有一个构造和一个析构的类来说显然不构成负担,但是如果一个庞大的类库,它有上百上千个方法时候,光是编写这些方法传递就有可能非常冗长,这也增加了出错的可能性。2、对于关注于性能的应用每一个方法都得有两层的函数调用,嵌套的开销也不理想3、由于句柄的存在,依然存在编译连接器兼容性问题。抽象接口使用了“接口与实现的分离”技术的Handle-Body解决了编译器/链接器的大部分问题,而C++面向对象编程中的抽象接口同样是运用了“接口与实现分离”的思想,而采用抽象接口对于解决这类问题是一个极其完美的解决方案。1、抽象接口的语言描述:classIMyString{virtualintLength()const=0;//这表示是一个纯虚函数,具有纯虚函数的接口virtualintIndex(constchar*psz)const=0;};2、抽象接口的内存结构:抽象接口采用虚函数表来调用成员方法。3、抽象接口的实现代码:接口:classIMyString实现:classCMyString:publicIMyString{private:constintm_cch;char*m_psz;public:CMyString(constchar*psz);virtual~CMyString();intLength()const;intIndex(constchar*psz)const;}从上面采用抽象接口的实例来看,抽象接口解决了Handle-Body所遗留下来的全部缺陷。抽象接口的一个典型应用:抽象工厂(AbstractFactroy)多继承与菱形缺陷、this跳转等多重继承

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论