已阅读5页,还剩13页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
.NET 4.0 中的契约式编程 契约式编程不是一门崭新的编程方法论。C/C+ 时代早已有之。Microsoft 在 .NET 4.0 中正式引入契约式编程库。博主以为契约式编程是一种相当不错的编程思想,每一个开发人员都应该掌握。它不但可以使开发人员的思维更清晰,而且对于提高程序性能很有帮助。值得一提的是,它对于并行程序设计也有莫大的益处。我们先看一段很简单的,未使用契约式编程的代码示例。/ .NET 代码示例public class RationalNumber private int numberator; private int denominator; public RationalNumber(int numberator, int denominator) this.numberator = numberator; this.denominator = denominator; public int Denominator get return this.denominator; 上述代码表示一个在 32 位有符号整型范围内的有理数。数学上,有理数是一个整数 a 和一个非零整数 b 的比,通常写作 a/b,故又称作分数(题外话:有理数这个翻译真是够奇怪)。由此,我们知道,有理数的分母不能为 0 。所以,上述代码示例的构造函数还需要写些防御性代码。通常 .NET 开发人员会这样写:/ .NET 代码示例public class RationalNumber private int numberator; private int denominator; public RationalNumber(int numberator, int denominator) if (denominator = 0) throw new ArgumentException(The second argument can not be zero.); this.numberator = numberator; this.denominator = denominator; public int Denominator get return this.denominator; 下面我们来看一下使用契约式编程的 .NET 4.0 代码示例。为了更加方便的说明,博主在整个示例上都加了契约,但此示例并非一定都加这些契约。/ .NET 代码示例public class RationalNumber private int numberator; private int denominator; public RationalNumber(int numberator, int denominator) Contract.Requires(denominator != 0, The second argument can not be zero.); this.numberator = numberator; this.denominator = denominator; public int Denominator get Contract.Ensures(Contract.Result() != 0); return this.denominator; ContractInvariantMethod protected void ObjectInvariant() Contract.Invariant(this.denominator != 0); 详细的解释稍后再说。按理,既然契约式编程有那么多好处,那在 C/C+ 世界应该很流行才对。为什么很少看到关于契约式编程的讨论呢?看一下 C+ 的契约式编程示例就知道了。下面是 C+ 代码示例:/typedef long int32_t;#include templateinline void CheckInvariant(T& argument)#ifdef CONTRACT_FULL argument.Invariant();#endifpublic class RationalNumberprivate: int32_t numberator; int32_t denominator;public: RationalNumber(int32_t numberator, int32_t denominator) #ifdef CONTRACT_FULL ASSERT(denominator != 0); CheckInvaraint(*this);#endif this.numberator = numberator; this.denominator = denominator;#ifdef CONTRACT_FULL CheckInvaraint(*this);#endif public: int32_t GetDenominator() #ifdef CONTRACT_FULL / C+ Developers like to use struct type. class Contract int32_t Result; Contract() Contract() #endif#ifdef CONTRACT_FULL Contract contract = new Contract(); contract.Result = denominator; CheckInvairant(*this);#endif return this.denominator;#ifdef CONTRACT_FULL CheckInvaraint(*this);#endif protected:#ifdef CONTRACT_FULL virtual void Invariant() this.denominator != 0; #endifWoo., 上述代码充斥了大量的宏和条件编译。对于习惯了 C# 优雅语法的 .NET 开发人员来说,它们是如此丑陋。更重要的是,契约式编程在 C+ 世界并未被标准化,因此项目之间的定义和修改各不一样,给代码造成很大混乱。这正是很少在实际中看到契约式编程应用的原因。但是在 .NET 4.0 中,契约式编程变得简单优雅起来。.NET 4.0 提供了契约式编程库。实际上,.NET 4.0 仅仅是针对 C+ 宏和条件编译的再次抽象和封装。它完全基于 CONTRACTS_FULL, CONTRACTS_PRECONDITIONS Symbol 和 System.Diagnostics.Debug.Assert 方法、System.Environment.FastFail 方法的封装。那么,何谓契约式编程?何谓契约式编程契约是减少大型项目成本的突破性技术。它一般由 Precondition(前置条件), Postcondition(后置条件) 和 Invariant(不变量) 等概念组成。.NET 4.0 除上述概念之外,还增加了 Assert(断言),Assume(假设) 概念。这可以由枚举 ContractFailureKind 类型一窥端倪。契约的思想很简单。它只是一组结果为真的表达式。如若不然,契约就被违反。那按照定义,程序中就存在纰漏。契约构成了程序规格说明的一部分,只不过该说明从文档挪到了代码中。开发人员都知道,文档通常不完整、过时,甚至不存在。将契约挪移到代码中,就使得程序可以被验证。正如前所述,.NET 4.0 对宏和条件编译进行抽象封装。这些成果大多集中在 System.Diagnostics.Contracts.Contract 静态类中。该类中的大多数成员都是条件编译。这样,我们就不用再使用 #ifdef 和定义 CONTRACTS_FULL 之类的标记。更重要的是,这些行为被标准化,可以在多个项目中统一使用,并根据情况是否生成带有契约的程序集。1. AssertAssert(断言)是最基本的契约。.NET 4.0 使用 Contract.Assert() 方法来特指断言。它用来表示程序点必须保持的一个契约。Contract.Assert(this.privateField 0);Contract.Assert(this.x = 3, Why isnt the value of x 3?);断言有两个重载方法,首参数都是一个布尔表达式,第二个方法的第二个参数表示违反契约时的异常信息。当断言运行时失败,.NET CLR 仅仅调用 Debug.Assert 方法。成功时则什么也不做。2. Assume.NET 4.0 使用 Contract.Assume() 方法表示 Assume(假设) 契约。Contract.Assume(this.privateField 0);Contract.Assume(this.x = 3, Static checker assumed this);Assume 契约在运行时检测的行为与 Assert(断言) 契约完全一致。但对于静态验证来说,Assume 契约仅仅验证已添加的事实。由于诸多限制,静态验证并不能保证该契约。或许最好先使用 Assert 契约,然后在验证代码时按需修改。当 Assume 契约运行时失败时, .NET CLR 会调用 Debug.Assert(false)。同样,成功时什么也不做。3. Preconditions.NET 4.0 使用 Contract.Requires() 方法表示 Preconditions(前置条件) 契约。它表示方法被调用时方法状态的契约,通常被用来做参数验证。所有 Preconditions 契约相关成员,至少方法本身可以访问。Contract.Requires(x != null);Preconditions 契约的运行时行为依赖于几个因素。如果只隐式定义了 CONTRACTS PRECONDITIONS 标记,而没有定义 CONTRACTS_FULL 标记,那么只会进行检测 Preconditions 契约,而不会检测任何 Postconditions 和 Invariants 契约。假如违反了 Preconditions 契约,那么 CLR 会调用 Debug.Assert(false) 和 Environment.FastFail 方法。假如想保证 Preconditions 契约在任何编译中都发挥作用,可以使用下面这个方法:Contract.RequiresAlways(x != null);为了保持向后兼容性,当已存在的代码不允许被修改时,我们需要抛出指定的精确异常。但是在 Preconditions 契约中,有一些格式上的限定。如下代码所示:if (x = null) throw new ArgumentException(The argument can not be null.);Contract.EndContractBlock(); / 前面所有的 if 检测语句皆是 Preconditions 契约这种 Preconditions 契约的格式严格受限:它必须严格按照上述代码示例格式。而且不能有 else 从句。此外,then 从句也只能有单个 throw 语句。最后必须使用 Contract.EndContractBlock() 方法来标记 Preconditions 契约结束。看到这里,是不是觉得大多数参数验证都可以被 Preconditions 契约替代?没有错,事实的确如此。这样这些防御性代码完全可以在 Release 被去掉,从而不用做那些冗余的代码检测,从而提高程序性能。但在面向验证客户输入此类情境下,防御性代码仍有必要。再就是,Microsoft 为了保持兼容性,并没有用 Preconditions 契约代替异常。4. PostconditionsPostconditions 契约表示方法终止时的状态。它跟 Preconditions 契约的运行时行为完全一致。但与 Preconditions 契约不同,Postconditions 契约相关的成员有着更少的可见性。客户程序或许不会理解或使用 Postconditions 契约表示的信息,但这并不影响客户程序正确使用 API 。对于 Preconditions 契约来说,它则对客户程序有副作用:不能保证客户程序不违反 Preconditions 契约。A. 标准 Postconditions 契约用法.NET 4.0 使用 Contract.Ensures() 方法表示标准 Postconditions 契约用法。它表示方法正常终止时必须保持的契约。Contract.Ensures(this.F 0);B. 特殊 Postconditions 契约用法当从方法体内抛出一个特定异常时,通常情况下 .NET CLR 会从方法体内抛出异常的位置直接跳出,从而辗转堆栈进行异常处理。假如我们需要在异常抛出时还要进行 Postconditions 契约验证,我们可以如下使用:Contract.EnsuresOnThrows(this.F 0);其中小括号内的参数表示当异常从方法内抛出时必须保持的契约,而泛型参数表示异常发生时抛出的异常类型。举例来说,当我们把 T 用 Exception 表示时,无论什么类型的异常被抛出,都能保证 Postconditions 契约。哪怕这个异常是堆栈溢出或任何不能控制的异常。强烈推荐当异常是被调用 API 一部分时,使用 Contract.EnsuresOnThrows() 方法。C. Postconditions 契约内的特殊方法以下要讲的这几个特殊方法仅限使用在 Postconditions 契约内。方法返回值 在 Postconditions 契约内,可以通过 Contract.Result() 方法表示,其中 T 表示方法返回类型。当编译器不能推导出 T 类型时,我们必须显式指出。比如,C# 编译器就不能推导出方法参数类型。Contract.Ensures(0 Contract.Result();假如方法返回 void ,则不必在 Postconditions 契约内使用 Contract.Result() 。前值(旧值) 在 Postconditions 契约内,通过 Contract.OldValue(e) 表示旧有值,其中 T 是 e 的类型。当编译器能够推导 T 类型时,可以忽略。此外 e 和旧有表达式出现上下文有一些限制。旧有表达式只能出现在 Postconditions 契约内。旧有表达式不能包含另一个旧有表达式。一个很重要的原则就是旧有表达式只能引用方法已经存在的那些旧值。比如,只要方法 Preconditions 契约持有,它必定能被计算。下面是这个原则的一些示例: 方法的旧有状态必定存在其值。比如 Preconditions 契约暗含 xs != null ,xs 当然可以被计算。但是,假如 Preconditions 契约为 xs != null | E(E 为任意表达式),那么 xs 就有可能不能被计算。 Contract.OldValue(xs.Length); / 很可能错误 方法返回值不能被旧有表达式引用。 Contract.OldValue(Contract.Result() + x); / 错误 out 参数也不能被旧有表达式引用。 如果某些标记的方法依赖方法返回值,那么这些方法也不能被旧有表达式引用。 Contract.ForAll(0, Contract.Result(), i = Contract.OldValue(xsi) 3); / 错误 旧有表达式不能在 Contract.ForAll() 和 Contract.Exists() 方法内引用匿名委托参数,除非旧有表达式被用作索引器或方法调用参数。 Contract.ForAll(0, xs.Length, i = Contract.OldValue(xsi) 3); / OKContract.ForAll(0, xs.Length, i = Contract.OldValue(i) 3); / 错误 如果旧有表达式依赖于匿名委托的参数,那么旧有表达式不能在匿名委托的方法体内。除非匿名委托是 Contract.ForAll() 和 Contract.Exists() 方法的参数。 Foo( . (T t) = Contract.OldValue(. t .) . ); / 错误D. out 参数因为契约出现在方法体前面,所以大多数编译器不允许在 Postconditions 契约内引用 out 参数。为了绕开这个问题,.NET 契约库提供了 Contract.ValueAtReturn(out T t) 方法。public void OutParam(out int x) Contract.Ensures(Contract.ValueAtReturn(out x) = 3); x = 3;跟 OldValue 一样,当编译器能推导出类型时,泛型参数可以被忽略。该方法只能出现在 Postconditions 契约。方法参数必须是 out 参数,且不允许使用表达式。需要注意的是,.NET 目前的工具不能检测确保 out 参数是否正确初始化,而不管它是否在 Postconditions 契约内。因此, x = 3 语句假如被赋予其他值时,编译器并不能发现错误。但是,当编译 Release 版本时,编译器将发现该问题。5. Object Invariants对象不变量表示无论对象是否对客户程序可见,类的每一个实例都应该保持的契约。它表示对象处于一个“良好”状态。在 .NET 4.0 中,对象的所有不变量都应当放入一个受保护的返回 void 的实例方法中。同时用ContractInvariantMethod特性标记该方法。此外,该方法体内在调用一系列 Contract.Invariant() 方法后不能再有其他代码。通常我们会把该方法命名为 ObjectInvariant 。ContractInvariantMethodprotected void ObjectInvariant() Contract.Invariant(this.y = 0); Contract.Invariant(this.x this.y);同样,Object Invariants 契约的运行时行为和 Preconditions 契约、Postconditions 契约行为一致。CLR 运行时会在每个公共方法末端检测 Object Invariants 契约,但不会检测对象终结器或任何实现 System.IDisposable 接口的方法。6. Contract 静态类中的其他特殊方法.NET 4.0 契约库中的 Contract 静态类还提供了几个特殊的方法。它们分别是:A. ForAllContract.ForAll() 方法有两个重载。第一个重载有两个参数:一个集合和一个谓词。谓词表示返回布尔值的一元方法,且该谓词应用于集合中的每一个元素。任何一个元素让谓词返回 false ,ForAll 停止迭代并返回 false 。否则, ForAll 返回 true 。下面是一个数组内所有元素都不能为 null 的契约示例:public T Foo(T array) Contract.Requires(Contract.ForAll(array, (T x) = x != null);B. Exists它和 ForAll 方法差不多。7. 接口契约因为 C#/VB 编译器不允许接口内的方法带有实现代码,所以我们如果想在接口中实现契约,需要创建一个帮助类。接口和契约帮助类通过一对特性来链接。如下所示:ContractClass(typeof(IFooContract)interface IFoo int Count get; void Put(int value);ContractClassFor(typeof(IFoo)sealed class IFooContract : IFoo int IFoo.Count get Contract.Ensures(Contract.Result() = 0); return default(int); / dummy return void IFoo.Put(int value) Contract.Requires(value = 0); .NET 需要显式如上述声明从而把接口和接口方法相关联起来。注意,我们不得不产生一个哑元返回值。最简单的方式就是返回 default(T),不要使用 Contract.Result 。由于 .NET 要求显式实现接口方法,所以在契约内引用相同接口的其他方法就显得很笨拙。由此,.NET 允许在契约方法之前,使用一个局部变量引用接口类型。如下所示:ContractClassFor(typeof(IFoo)sealed class IFooContract : IFoo int IFoo.Count get Contract.Ensures(Contract.Result() = 0); return default(int); / dummy return void IFoo.Put(int value) IFoo iFoo = this; Contract.Requires(value = 0); Contract.Requires(iFoo.Count 10); / 否则的话,就需要强制转型 (IFoo)this).Count 8. 抽象方法契约同接口类似,.NET 中抽象类中的抽象方法也不能包含方法体。所以同接口契约一样,需要帮助类来完成契约。代码示例不再给出。9. 契约方法重载所有的契约方法都有一个带有 string 类型参数的重载版本。如下所示:Contract.Requires(obj != null, if obj is null, then missil
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 山东海洋文化旅游发展集团有限公司2025年度公开招聘拟聘用人员笔试历年典型考点题库附带答案详解
- 白山市2025吉林白山市事业单位招聘高层次和急需紧缺人才166人(1号)笔试历年参考题库典型考点附带答案详解
- 泸州市发布《泸州市2025年秋季企事业单位人才岗位需求信息》(6539人)笔试历年参考题库典型考点附带答案详解
- 曲靖市2025云南省曲靖市委党校青年人才专项引进笔试历年参考题库典型考点附带答案详解
- 广东省2025广东省农业科学院农业经济与信息研究所招聘劳动合同制工作人员2人笔试历年参考题库典型考点附带答案详解
- AI在智能建造中的应用
- AI在虚拟现实技术中的应用
- 2026糖尿病荞麦食用注意事项课件
- 工业旅游旅游产品定制协议
- 产后出血的护理服务与质量管理
- DB64+1858-2022+农业气象观测规范宁夏菜心
- 建立模糊专家系统实验报告
- 爱情片《百万英镑》台词-中英文对照
- 基于solidworks的齿轮泵仿真
- 半导体物理学(刘恩科)第七版-完整课后题答案
- 政策监控案例北京动物园搬迁风波
- 基础生态学-群落的组成与结构
- 理气药的药理作用(中药药理学课件)
- 霍金斯能量层级(全)
- T-SXDZ 057-2020 煤矿冲击地压危险性评价报告编制细则
- GB/T 25146-2010工业设备化学清洗质量验收规范
评论
0/150
提交评论