|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
你觉得数据库怎么样?左券式编程不是一门极新的编程办法论。C/C++时期早已有之。Microsoft在.NET4.0中正式引进左券式编程库。博主觉得左券式编程是一种相称不错的编程头脑,每个开辟职员都应当把握。它不仅可使开辟职员的头脑更明晰,并且关于进步程序功能很有匡助。值得一提的是,它关于并路程序计划也有莫年夜的好处。
我们先看一段很复杂的,未利用左券式编程的代码示例。- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{returnthis.denominator;}}}
复制代码 上述代码暗示一个在32位有标记整型局限内的有理数。数学上,有理数是一个整数a和一个非零整数b的比,一般写作a/b,故又称作分数(题外话:有理数这个翻译真是够奇异)。由此,我们晓得,有理数的分母不克不及为0。以是,上述代码示例的机关函数还必要写些进攻性代码。一般.NET开辟职员会如许写:- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){if(denominator==0)thrownewArgumentException("Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{returnthis.denominator;}}}
复制代码 上面我们来看一下利用左券式编程的.NET4.0代码示例。为了加倍便利的申明,博主在全部示例上都加了左券,但此示例并不是必定都加这些左券。- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){Contract.Requires(denominator!=0,"Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{Contract.Ensures(Contract.Result<int>()!=0);returnthis.denominator;}}[ContractInvariantMethod]protectedvoidObjectInvariant(){Contract.Invariant(this.denominator!=0);}}
复制代码 具体的注释稍后再说。按理,既然左券式编程有那末多优点,那在C/C++天下应当很盛行才对。为何很少看到关于左券式编程的会商呢?看一下C++的左券式编程示例就晓得了。上面是C++代码示例:- //typedeflongint32_t;#include<stdint.h>templateinlinevoidCheckInvariant(T&argument){#ifdefCONTRACT_FULLargument.Invariant();#endif}publicclassRationalNumber{private:int32_tnumberator;int32_tdenominator;public:RationalNumber(int32_tnumberator,int32_tdenominator){#ifdefCONTRACT_FULLASSERT(denominator!=0);CheckInvaraint(*this);#endifthis.numberator=numberator;this.denominator=denominator;#ifdefCONTRACT_FULLCheckInvaraint(*this);#endif}public:int32_tGetDenominator(){#ifdefCONTRACT_FULL//C++Developersliketousestructtype.classContract{int32_tResult;Contract(){}~Contract(){}}#endif#ifdefCONTRACT_FULLContractcontract=newContract();contract.Result=denominator;CheckInvairant(*this);#endifreturnthis.denominator;#ifdefCONTRACT_FULLCheckInvaraint(*this);#endif}protected:#ifdefCONTRACT_FULLvirtualvoidInvariant(){this.denominator!=0;}#endif}
复制代码 Woo...,上述代码充溢了大批的宏和前提编译。关于习气了C#文雅语法的.NET开辟职员来讲,它们是云云丑恶。更主要的是,左券式编程在C++天下并未被尺度化,因而项目之间的界说和修正各纷歧样,给代码形成很年夜凌乱。这恰是很少在实践中看到左券式编程使用的缘故原由。可是在.NET4.0中,左券式编程变得复杂文雅起来。.NET4.0供应了左券式编程库。实践上,.NET4.0仅仅是针对C++宏和前提编译的再次笼统和封装。它完整基于CONTRACTS_FULL,CONTRACTS_PRECONDITIONSSymbol和System.Diagnostics.Debug.Assert办法、System.Environment.FastFail办法的封装。
那末,何谓左券式编程?
何谓左券式编程
左券是削减年夜型项目本钱的冲破性手艺。它一样平常由Precondition(前置前提),Postcondition(后置前提)和Invariant(稳定量)等观点构成。.NET4.0除上述观点以外,还增添了Assert(断言),Assume(假定)观点。这能够由列举ContractFailureKind范例一窥眉目。
左券的头脑很复杂。它只是一组了局为真的表达式。如若否则,左券就被违背。那依照界说,程序中就存在忽略。左券组成了程序规格申明的一部分,只不外该申明从文档挪到了代码中。开辟职员都晓得,文档一般不完全、过期,乃至不存在。将左券移动到代码中,就使得程序能够被考证。
正如前所述,.NET4.0对宏和前提编译举行笼统封装。这些功效年夜多会合在System.Diagnostics.Contracts.Contract静态类中。该类中的年夜多半成员都是前提编译。如许,我们就不必再利用#ifdef和界说CONTRACTS_FULL之类的标志。更主要的是,这些举动被尺度化,能够在多个项目中一致利用,并依据情形是不是天生带有左券的程序集。
1.Assert
Assert(断言)是最基础的左券。.NET4.0利用Contract.Assert()办法来特指断言。它用来暗示程序点必需坚持的一个左券。- Contract.Assert(this.privateField>0);Contract.Assert(this.x==3,"Whyisn’tthevalueofx3?");
复制代码 断言有两个重载办法,首参数都是一个布尔表达式,第二个办法的第二个参数暗示违背左券时的非常信息。
当断言运转时失利,.NETCLR仅仅挪用Debug.Assert办法。乐成时则甚么也不做。
2.Assume
.NET4.0利用Contract.Assume()办法暗示Assume(假定)左券。- Contract.Assume(this.privateField>0);Contract.Assume(this.x==3,"Staticcheckerassumedthis");
复制代码 Assume左券在运转时检测的举动与Assert(断言)左券完整分歧。但关于静态考证来讲,Assume左券仅仅考证已增加的现实。因为诸多限定,静态考证其实不能包管该左券。也许最好先利用Assert左券,然后在考证代码时按需修正。
当Assume左券运转时失利时,.NETCLR会挪用Debug.Assert(false)。一样,乐成时甚么也不做。
3.Preconditions
.NET4.0利用Contract.Requires()办法暗示Preconditions(前置前提)左券。它暗示办法被挪用时办法形态的左券,一般被用来做参数考证。一切Preconditions左券相干成员,最少办法自己能够会见。- Contract.Requires(x!=null);
复制代码 Preconditions左券的运转时举动依附于几个要素。假如只隐式界说了CONTRACTSPRECONDITIONS标志,而没有界说CONTRACTS_FULL标志,那末只会举行检测Preconditions左券,而不会检测任何Postconditions和Invariants左券。假设违背了Preconditions左券,那末CLR会挪用Debug.Assert(false)和Environment.FastFail办法。
假设想包管Preconditions左券在任何编译中都发扬感化,可使用上面这个办法:- Contract.RequiresAlways(x!=null);
复制代码 为了坚持向后兼容性,当已存在的代码不同意被修正时,我们必要抛出指定的准确非常。可是在Preconditions左券中,有一些格局上的限制。以下代码所示:- if(x==null)thrownewArgumentException("Theargumentcannotbenull.");Contract.EndContractBlock();//后面一切的if检测语句皆是Preconditions左券
复制代码 这类Preconditions左券的格局严厉受限:它必需严厉依照上述代码示例格局。并且不克不及有else从句。别的,then从句也只能有单个throw语句。最初必需利用Contract.EndContractBlock()办法来标志Preconditions左券停止。
看到这里,是否是以为年夜多半参数考证都能够被Preconditions左券替换?没有错,现实切实其实云云。如许这些进攻性代码完整能够在Release被往失落,从而不必做那些冗余的代码检测,从而进步程序功能。但在面向考证客户输出此类情境下,进攻性代码仍有需要。再就是,Microsoft为了坚持兼容性,并没有效Preconditions左券取代非常。
4.Postconditions
Postconditions左券暗示办法停止时的形态。它跟Preconditions左券的运转时举动完整分歧。但与Preconditions左券分歧,Postconditions左券相干的成员有着更少的可见性。客户程序也许不会了解或利用Postconditions左券暗示的信息,但这其实不影响客户程序准确利用API。
关于Preconditions左券来讲,它则对客户程序有反作用:不克不及包管客户程序不违背Preconditions左券。
A.尺度Postconditions左券用法
.NET4.0利用Contract.Ensures()办法暗示尺度Postconditions左券用法。它暗示办法一般停止时必需坚持的左券。- Contract.Ensures(this.F>0);
复制代码 B.特别Postconditions左券用法
当从办法体内抛出一个特定非常时,一般情形下.NETCLR会从办法体内抛出非常的地位间接跳出,从而展转仓库举行非常处置。假设我们必要在非常抛出时还要举行Postconditions左券考证,我们能够以下利用:- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){if(denominator==0)thrownewArgumentException("Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{returnthis.denominator;}}}0
复制代码 个中小括号内的参数暗示当非常从办法内抛出时必需坚持的左券,而泛型参数暗示非常产生时抛出的非常范例。举例来讲,当我们把T用Exception暗示时,不管甚么范例的非常被抛出,都能包管Postconditions左券。哪怕这个非常是仓库溢出或任何不克不及把持的非常。激烈保举当非常是被挪用API一部分时,利用Contract.EnsuresOnThrows<T>()办法。
C.Postconditions左券内的特别办法
以下要讲的这几个特别办法仅限利用在Postconditions左券内。
办法前往值在Postconditions左券内,能够经由过程Contract.Result<T>()办法暗示,个中T暗示办法前往范例。当编译器不克不及推导出T范例时,我们必需显式指出。好比,C#编译器就不克不及推导出办法参数范例。- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){if(denominator==0)thrownewArgumentException("Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{returnthis.denominator;}}}1
复制代码 假设办法前往void,则不用在Postconditions左券内利用Contract.Result<T>()。
前值(旧值)在Postconditions左券内,经由过程Contract.OldValue<T>(e)暗示旧有值,个中T是e的范例。当编译器可以推导T范例时,能够疏忽。别的e和旧有表达式呈现高低文有一些限定。旧有表达式只能呈现在Postconditions左券内。旧有表达式不克不及包括另外一个旧有表达式。一个很主要的准绳就是旧有表达式只能援用办法已存在的那些旧值。好比,只需办法Preconditions左券持有,它一定能被盘算。上面是这个准绳的一些示例:
- 办法的旧有形态一定存在其值。好比Preconditions左券暗含xs!=null,xs固然能够被盘算。可是,假设Preconditions左券为xs!=null||E(E为恣意表达式),那末xs就有大概不克不及被盘算。
- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){if(denominator==0)thrownewArgumentException("Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{returnthis.denominator;}}}2
复制代码- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){if(denominator==0)thrownewArgumentException("Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{returnthis.denominator;}}}3
复制代码
- out参数也不克不及被旧有表达式援用。
- 假如某些标志的办法依附办法前往值,那末这些办法也不克不及被旧有表达式援用。
- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){if(denominator==0)thrownewArgumentException("Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{returnthis.denominator;}}}4
复制代码
- 旧有表达式不克不及在Contract.ForAll()和Contract.Exists()办法内援用匿名托付参数,除非旧有表达式被用作索引器或办法挪用参数。
- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){if(denominator==0)thrownewArgumentException("Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{returnthis.denominator;}}}5
复制代码- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){if(denominator==0)thrownewArgumentException("Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{returnthis.denominator;}}}6
复制代码
- 假如旧有表达式依附于匿名托付的参数,那末旧有表达式不克不及在匿名托付的办法体内。除非匿名托付是Contract.ForAll()和Contract.Exists()办法的参数。
- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){if(denominator==0)thrownewArgumentException("Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{returnthis.denominator;}}}7
复制代码 D.out参数
由于左券呈现在办法体后面,以是年夜多半编译器不同意在Postconditions左券内援用out参数。为了绕开这个成绩,.NET左券库供应了Contract.ValueAtReturn<T>(outTt)办法。- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){if(denominator==0)thrownewArgumentException("Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{returnthis.denominator;}}}8
复制代码 跟OldValue一样,当编译器能推导出范例时,泛型参数能够被疏忽。该办法只能呈现在Postconditions左券。办法参数必需是out参数,且不同意利用表达式。
必要注重的是,.NET今朝的工具不克不及检测确保out参数是不是准确初始化,而不论它是不是在Postconditions左券内。因而,x=3语句假设被付与其他值时,编译器其实不能发明毛病。可是,当编译Release版本时,编译器将发明该成绩。
5.ObjectInvariants
对象稳定量暗示不管对象是不是对客户程序可见,类的每个实例都应当坚持的左券。它暗示对象处于一个“优秀”形态。
在.NET4.0中,对象的一切稳定量都应该放进一个受回护的前往void的实例办法中。同时用[ContractInvariantMethod]特征标志该办法。别的,该办法体内涵挪用一系列Contract.Invariant()办法后不克不及再有其他代码。一般我们会把该办法定名为ObjectInvariant。- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){if(denominator==0)thrownewArgumentException("Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{returnthis.denominator;}}}9
复制代码 一样,ObjectInvariants左券的运转时举动和Preconditions左券、Postconditions左券举动分歧。CLR运转时会在每一个大众办法末了检测ObjectInvariants左券,但不会检测对象闭幕器或任何完成System.IDisposable接口的办法。
6.Contract静态类中的其他特别办法
.NET4.0左券库中的Contract静态类还供应了几个特别的办法。它们分离是:
A.ForAll
Contract.ForAll()办法有两个重载。第一个重载有两个参数:一个汇合和一个谓词。谓词暗示前往布尔值的一元办法,且该谓词使用于汇合中的每个元素。任何一个元素让谓词前往false,ForAll中断迭代并前往false。不然,ForAll前往true。上面是一个数组内一切元素都不克不及为null的左券示例:- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){Contract.Requires(denominator!=0,"Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{Contract.Ensures(Contract.Result<int>()!=0);returnthis.denominator;}}[ContractInvariantMethod]protectedvoidObjectInvariant(){Contract.Invariant(this.denominator!=0);}}0
复制代码 B.Exists
它和ForAll办法差未几。
7.接口左券
由于C#/VB编译器不同意接口内的办法带有完成代码,以是我们假如想在接口中完成左券,必要创立一个匡助类。接口和左券匡助类经由过程一对特征来链接。以下所示:- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){Contract.Requires(denominator!=0,"Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{Contract.Ensures(Contract.Result<int>()!=0);returnthis.denominator;}}[ContractInvariantMethod]protectedvoidObjectInvariant(){Contract.Invariant(this.denominator!=0);}}1
复制代码 .NET必要显式如上述声明从而把接口和接口办法相干联起来。注重,我们不能不发生一个哑元前往值。最复杂的体例就是前往default(T),不要利用Contract.Result<T>。
因为.NET请求显式完成接口办法,以是在左券内援用不异接口的其他办法就显得很愚笨。由此,.NET同意在左券办法之前,利用一个部分变量援用接口范例。以下所示:- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){Contract.Requires(denominator!=0,"Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{Contract.Ensures(Contract.Result<int>()!=0);returnthis.denominator;}}[ContractInvariantMethod]protectedvoidObjectInvariant(){Contract.Invariant(this.denominator!=0);}}2
复制代码 8.笼统办法左券
同接口相似,.NET中笼统类中的笼统办法也不克不及包括办法体。以是同接口左券一样,必要匡助类来完成左券。代码示例不再给出。
9.左券办法重载
一切的左券办法都有一个带有string范例参数的重载版本。以下所示:- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){Contract.Requires(denominator!=0,"Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{Contract.Ensures(Contract.Result<int>()!=0);returnthis.denominator;}}[ContractInvariantMethod]protectedvoidObjectInvariant(){Contract.Invariant(this.denominator!=0);}}3
复制代码 如许当左券被违背时,.NET能够在运转时供应一个信息提醒。今朝,该字符串只能是编译经常量。可是,未来.NET大概会改动,字符串能够运转时被盘算。可是,假如是字符串常量,静态诊断工具能够选择显现它。
10.左券特征
A.ContractClass和ContractClassFor
这两个特征,我们已在接口左券和笼统办法左券里看到了。ContractClass特征用于增加到接口或笼统范例上,可是指向的倒是完成该范例的匡助类。ContractClassFor特征用来增加到匡助类上,指向我们必要左券考证的接口或笼统范例。
B.ContractInvariantMethod
这个特征用来标志暗示对象稳定量的办法。
C.Pure
Pure特征只声明在那些没有反作用的办法挪用者上。.NET现存的一些托付能够被以为云云,好比System.Predicate<T>和System.Comparison<T>。
D.RuntimeContracts
这是个程序集级其余特征(详细怎样,俺也不太分明)。
E.ContractPublicPropertyName
这个特征用在字段上。它被用在办法左券中,且该办法相对字段来讲,更具可见性。好比公有字段和大众办法。以下所示:- //.NET代码示例publicclassRationalNumber{privateintnumberator;privateintdenominator;publicRationalNumber(intnumberator,intdenominator){Contract.Requires(denominator!=0,"Thesecondargumentcannotbezero.");this.numberator=numberator;this.denominator=denominator;}publicintDenominator{get{Contract.Ensures(Contract.Result<int>()!=0);returnthis.denominator;}}[ContractInvariantMethod]protectedvoidObjectInvariant(){Contract.Invariant(this.denominator!=0);}}4
复制代码 F.ContractVerification
这个特征用来假定程序集、范例、成员是不是可被考证实行。我们可使用[ContractVerification(false)]来显式标志程序集、范例、成员不被考证实行。
.NET左券库今朝的缺点
接上去,讲一讲.NET左券库今朝所存在的一些成绩。
- 值范例中的稳定量是被疏忽的,不发扬感化。
- 静态检测还不克不及处置Contract.ForAll()和Contract.Exists()办法。
- C#迭代器中的左券成绩。我们晓得Microsoft在C#2.0中增加了yield关头字来匡助我们完成迭代功效。它实际上是C#编译器做的糖果。如今左券中,呈现了成绩。编译器发生的代码会把我们写的左券放进到MoveNext()办法中。这个时侯,静态检测就不克不及包管可以准确完成Preconditions左券。
Well,.NET左券式编程到这里就停止了。嗯,就到这里了。
PS:.NET左券库固然已相称文雅。但博主觉得,其跟D言语完成的左券式编程仍有一段间隔。
PS:有谁乐意当俺的Mentor。您可以享用如许的权力和任务:天堂般可怕的发问和骚扰。非诲人不倦者勿扰。
本文来自:http://www.ckuyun.com/lucifer1982/archive/2009/03/21/1418642.html
相干浏览:
.NET代码左券组件今朝已供应下载
一个很大的类库。应用程序之所以难以跨平台,在于直接调用了特定平台的接口,而一个巨大的类库,就能极大地减少应用程序对平台的依赖。 |
|