c#Code Contracts代码协定
Code Contracts的命名空间:System.Diagnostics.Contracts
集合1.
安装Code Contracts for .NET插件
Contracts.devlab9ts.msi
安装了该插件后,可以在项目的属性页上发现多了一个Code Contracts的标签:
勾上"Perform Runtime Check"选项,只是可以看到右侧的下拉框有五个选项,这里分别介绍一下它们的区别:
- Full表示执行所有的代码协定语句。
- Pre and Post表示执行前置和后置条件检查,即Contract.Require和Contract.Ensures。
- Preconditions 表示只执行前置条件检查,即Contract.Require。
- ReleaseRequires 表示执行public类的public方法的前置条件检查。
- None表示不执行代码协定检查,即不进行代码协定注入。
之所以有这些选项,是因为代码协定的代码注入运行时候是有开销的,影响运行性能。另外,链接的时候注入代码也是影响程序的生成时间的。我们可以在需要检验功能的时候放开代码检查,需要性能的时候关闭检查,非常方便,而传统的手动抛异常的方式就做不到这一点。
值得一提的是,它是可以在Debug版本和Release版本中选择不同的开关的。我们在Debug版本中开启必要的代码检查,而在Release版本中关闭检查。需要注意的是,public类的public方法的入参检查(前置条件协定)是有必要的,即使在Release版本中也也应该存在。我的个人建议是在Debug版本中平时使用Pre and Post选项(注入代码也是影响程序的生成时间的,故只在有必要的时候才使用Full的方式),在Release版本中使用ReleaseRequires选项。
另外,在安装了Code Contracts for .NET插件后,它也顺带安装了一些代码片段,方便我们快速输入,非常给力。例如Contract.Require函数可以直接通过"cr"+Tab键输入,Contract.Ensures可以通过"ce"+Tab键输入。
集合2.
Project Setup
After you’ve downloaded and installed Code Contracts and created a library project, you need to enable CC for that library. Under the Project Properties, there is a new tab called Code Contracts. Here’s the way that I like to set it up:
- Set “Assembly Mode” to “Standard Contract Requires”.
- Debug - Check “Perform Runtime Contract Checking” and set to “Full”. Check “Call-site Requires Checking”.
- Release - Set “Contract Reference Assembly” to “Build” and check “Emit contracts into XML doc file”.
In addition, if you have the Academic or Commercial Premium edition of Code Contracts, add a configuration called CodeAnalysis with all the settings from Debug and also check “Perform Static Contract Checking” and all the other checkboxes in that section except “Baseline”.
This will give you three separate builds, with separate behavior:
- CodeAnalysis - This evaluates all the code contracts at build time, searching for any errors. This “build” doesn’t result in a usable dll, but should be run before checking in code, similar to a unit test.
- Debug - This turns on all code contracts at runtime. This includes code contract checks for any libraries that your library uses (such as CLR libraries). It also turns on consistency checks such as Contract.Assert and ContractInvariantMethod.
- Release - This turns off all code contracts in your dll at runtime. However, it includes a separate “.Contracts.dll” which contains the contracts for your library’s public API.
Projects consuming your library should reference your Release build. In their Debug configuration, they should check “Perform Runtime Contract Checking” and “Call-site Requires Checking”; this will ensure that the code contracts for your library’s public API are enforced at runtime (the “Call-site” option uses the “.Contracts.dll” assembly that you built). In their Release configuration, they should leave code contracts disabled, which allows all assemblies to run at full speed.
If the consuming project suspects a bug in your library (i.e., their Debug build doesn’t cause any Contracts violations but your library is still not behaving as expected), they can remove the reference to your Release build and add a reference to your Debug build. This is an easy way to enable all the code contract checks in your library, even the internal ones.
Preconditions (Contract.Requires)
Preconditions require some condition at the beginning of a method. Common examples are requiring a parameter to be non-null, or to require the object to be in a particular state.
public string GetObjectInfo(object obj)
{
Contract.Requires(obj != null);
return obj.ToString();
}
Postconditions (Contract.Ensures)
Postconditions guarantee some condition at the end of a method. It is often used with Contract.Result to guarantee that a particular method won’t return null.
private string name; // never null
public string Name
{
get
{
Contract.Ensures(Contract.Result<string>() != null);
return this.name;
}
}
Preconditions and Postconditions on Interfaces
Both preconditions and postconditions are commonly placed on interface members. Code Contracts includes the ContractClassAttribute and ContractClassForAttribute to facilitate this:
[ContractClass(typeof(MyInterfaceContracts))]
public interface IMyInterface
{
string GetObjectInfo(object obj);
string Name { get; }
}
[ContractClassFor(typeof(IMyInterface))]
internal abstract class MyInterfaceContracts : IMyInterface
{
public string GetObjectInfo(object obj)
{
Contract.Requires(obj != null);
return null; // fake implementation
}
public string Name
{
get
{
Contract.Ensures(Contract.Result<string>() != null);
return null; // fake implementation does not need to satisfy postcondition
}
}
}
With generic interfaces, the same idea holds:
[ContractClass(typeof(MyInterfaceContracts<,>))]
public interface IMyInterface<T, U>
{
string GetObjectInfo(object obj);
string Name { get; }
}
[ContractClassFor(typeof(IMyInterface<,>))]
internal abstract class MyInterfaceContracts<T, U> : IMyInterface<T, U>
{
public string GetObjectInfo(object obj)
{
Contract.Requires(obj != null);
return null; // fake implementation
}
public string Name
{
get
{
Contract.Ensures(Contract.Result<string>() != null);
return null; // fake implementation does not need to satisfy postcondition
}
}
}
Invariants (ContractInvariantMethod, Contract.Invariant)
Object invariants are expressed using the ContractInvariantMethod. If they are enabled by the build, then they are checked at the beginning of each method (except constructors) and at the end of each method (except Dispose and the finalizer).
public class MyClass<T, U>: public IMyInterface<T, U>
{
private string name;
public MyClass(string name)
{
Contract.Requires(name != null);
this.name = name;
}
[ContractInvariantMethod]
private void ObjectInvariant()
{
Contract.Invariant(this.name != null);
}
public string Name
{
get
{
Contract.Ensures(Contract.Result<string>() != null);
return this.name;
}
}
public string GetObjectInfo(object obj)
{
Contract.Requires(obj != null);
return obj.ToString();
}
}
Assertions and Assumptions (Contract.Assert, Contract.Assume)
There will always be some things that should be true but just have to be checked at runtime. For these, use Contract.Assert unless the static checker (i.e., the CodeAnalysis configuration) complains. You can then change them to be Contract.Assume so that the static checker can use them. There’s no difference between Contract.Assert and Contract.Assume at runtime.
Reminder: if you’re using the Release build setup recommended above, then all your Contract.Assertand Contract.Assume calls get removed from your release builds. So they can’t be used to throw vexing exceptions, e.g., rejecting invalid input.
In the example below, the static checker would complain because Type.MakeGenericType has preconditions that are difficult to prove. So we give it a little help by inserting some Contract.Assumecalls, and the static checker is then pacified.
public static IMyInterface<T, U> CreateUsingReflection()
{
var openGenericReturnType = typeof(MyClass<,>);
Contract.Assume(openGenericReturnType.IsGenericTypeDefinition);
Contract.Assume(openGenericReturnType.GetGenericArguments().Length == 2);
var constructedGenericReturnType = openGenericReturnType.MakeGenericType(typeof(T), typeof(U));
return (IMyInterface<T, U>)Activator.CreateInstance(constructedGenericReturnType);
}
For More Information
The Code Contracts library has a thorough user manual available. It’s a bit of a hard read, but they include a lot of information that I’ve skipped for this “intro” post, such as:
- Specifying postconditions that hold even if the method throws an exception.
- Techniques for gradually migrating Code Contracts into an existing library.
- Details on how Code Contracts are inherited.
- Contract abbreviations.
- Applying contracts to sequences (e.g., ForAll and Exists quantifiers).
- Advanced contract checking with Pure methods.
- Tips for working with the static checker.
集合3
1. Assert
Assert(断言)是最基本的契约。.NET 4.0 使用 Contract.Assert() 方法来特指断言。它用来表示程序点必须保持的一个契约。
Contract.Assert(this.privateField > 0);
Contract.Assert(this.x == 3, "Why isn’t 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. Postconditions
Postconditions 契约表示方法终止时的状态。它跟 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<T>(this.F > 0);
其中小括号内的参数表示当异常从方法内抛出时必须保持的契约,而泛型参数表示异常发生时抛出的异常类型。举例来说,当我们把 T 用 Exception 表示时,无论什么类型的异常被抛出,都能保证 Postconditions 契约。哪怕这个异常是堆栈溢出或任何不能控制的异常。强烈推荐当异常是被调用 API 一部分时,使用 Contract.EnsuresOnThrows<T>() 方法。
C. Postconditions 契约内的特殊方法
以下要讲的这几个特殊方法仅限使用在 Postconditions 契约内。
方法返回值 在 Postconditions 契约内,可以通过 Contract.Result<T>() 方法表示,其中 T 表示方法返回类型。当编译器不能推导出 T 类型时,我们必须显式指出。比如,C# 编译器就不能推导出方法参数类型。
Contract.Ensures(0 < Contract.Result<int>());
假如方法返回 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 就有可能不能被计算。
Contract.OldValue(xs.Length); // 很可能错误
- 方法返回值不能被旧有表达式引用。
Contract.OldValue(Contract.Result<int>() + x); // 错误
- out 参数也不能被旧有表达式引用。
- 如果某些标记的方法依赖方法返回值,那么这些方法也不能被旧有表达式引用。
Contract.ForAll(0, Contract.Result<int>(), i => Contract.OldValue(xs[i]) > 3); // 错误
- 旧有表达式不能在 Contract.ForAll() 和 Contract.Exists() 方法内引用匿名委托参数,除非旧有表达式被用作索引器或方法调用参数。
Contract.ForAll(0, xs.Length, i => Contract.OldValue(xs[i]) > 3); // OK
Contract.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<T>(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 。
[ContractInvariantMethod]
protected void ObjectInvariant()
{
Contract.Invariant(this.y >= 0);
Contract.Invariant(this.x > this.y);
}
同样,Object Invariants 契约的运行时行为和 Preconditions 契约、Postconditions 契约行为一致。CLR 运行时会在每个公共方法末端检测 Object Invariants 契约,但不会检测对象终结器或任何实现 System.IDisposable 接口的方法。
Contract 静态类中的其他特殊方法
.NET 4.0 契约库中的 Contract 静态类还提供了几个特殊的方法。它们分别是:
A. ForAll
Contract.ForAll() 方法有两个重载。第一个重载有两个参数:一个集合和一个谓词。谓词表示返回布尔值的一元方法,且该谓词应用于集合中的每一个元素。任何一个元素让谓词返回 false ,ForAll 停止迭代并返回 false 。否则, ForAll 返回 true 。下面是一个数组内所有元素都不能为 null 的契约示例:
public T[] Foo<T>(T[] array)
{
Contract.Requires(Contract.ForAll(array, (T x) => x != null));
}
B. Exists
它和 ForAll 方法差不多。
注:摘自
http://www.cnblogs.com/lucifer1982/archive/2009/03/21/1418642.html
http://blog.stephencleary.com/2011/01/simple-and-easy-code-contracts.html
c#Code Contracts代码协定的更多相关文章
- Code Snippets 代码片段
Code Snippets 代码片段 1.Title : 代码片段的标题 2.Summary : 代码片段的描述文字 3.Platform : 可以使用代码片段的平台,有IOS/OS X/ ...
- Effective Java提升Code Coverage代码涵盖率 - 就是爱Java
虽然我们已经有了测试程序,但是如何得知是否已完整测试了主程序?,透过Code Coverage代码涵盖率,我们可以快速地得知,目前系统中,有多少程序中被测试过,不考虑成本跟投资效益比,涵盖率越高,代表 ...
- 自动生成Code First代码
自动生成Code First代码 在前面的文章中我们提到Entity Framework的“Code First”模式也同样可以基于现有数据库进行开发.今天就让我们一起看一下使用Entity Fram ...
- 第五次作业2、请将该code进行代码重构,使之模块化,并易于阅读和维护;
1.请运行下面code,指出其功能: (需附运行结果截图,并用简短文字描述其功能) 显示了人的姓名.年龄 2.请将该code进行代码重构,使之模块化,并易于阅读和维护: 3.观看视频The Exper ...
- Code::Blocks代码自动提示设置及常用快捷键
Code::Blocks代码自动提示设置及常用快捷键(适用windows和linux) 1)以下需要设置的地方均在Settings->Editor...弹出的对话框中. 2)不少命令都可针对当前 ...
- VS Code 用户自定义代码片段(React)
VS Code 用户自定义代码片段(React) .jsxReact组件模板:javascriptreact.json { "Import React": { "pref ...
- dead code 死代码 无作用的代码
DatasetVector datasetvector=(DatasetVector)dataset; if (datasetvector == null) ...
- IDEA工具java开发之 常用插件 git插件 追加提交 Code Review==代码评审插件 撤销提交 撤销提交 关联远程仓库 设置git 本地操作
◆git 插件 请先安装git for windows ,git客户端工具 平时开发中,git的使用都是用可视化界面,git命令需要不时复习,以备不时之需 1.环境准备 (1)设置git (2)本地操 ...
- VS code调试代码快速上手必备知识
一.通过简单的配置运行一个JavaScript程序 1.打开(创建)一个新的工作空间(文件夹),并创建一个js文件: var name='world'; var s='Hello,${name}!'; ...
随机推荐
- js进阶 11-13 jquery如何包裹元素和去除元素外的包裹
js进阶 11-13 jquery如何包裹元素和去除元素外的包裹 一.总结 一句话总结:wrap().wrapAll().unwrap().innerWrap()四个方法 1.jquery中unwr ...
- COCOS学习笔记--单点触控
这篇博客来总结下cocos单点触控的相关内容: 我们在Layer类的源代码中能够看到.Layer类提供了一下4个与屏幕单点触控相关的回调方法: onTouchBegan().onTouchMoved( ...
- 【u028】数列的整除性
Time Limit: 1 second Memory Limit: 128 MB [问题描述] 对于任意一个整数数列,我们可以在每两个整数中间任意放一个符号'+'或'-',这样就可以构成一个表达式, ...
- [Ramda] Get Deeply Nested Properties Safely with Ramda's path and pathOr Functions
In this lesson we'll see how Ramda's path and pathOr functions can be used to safely access a deeply ...
- Android 离线语音用法(讯飞语音)
这次给大家带来的是项目的离线语音功能. 讯飞开放平台中的离线语音 首先创建开放平台的账号.这个不必多说 然后创建新应用 选择我的应用,例如以下图,注意下我打马赛克的地方,这个appId非常重要 点击进 ...
- DOM常用的四大对象是什么?
DOM常用的四大对象是什么? 一.总结 一句话总结: 1.关注结构,关注主干 2.从主干处着手的话,可以发现dom就是四个东西,document(文档),element,attribute,event ...
- MySql批量drop table
原文:MySql批量drop table 今天发现数据库中很多没用的表,想清理掉. 发现mysql好像不支持类似这样的写法:drop table like "%r" 在oracle ...
- C语言编写静态链接库及其使用
本篇讲述使用C语言编写静态链接库,而且使用C和C++的方式来调用等. 一.静态库程序:执行时不独立存在,链接到可执行文件或者动态库中,目标程序的归档. 1.用C编写静态库步骤 a.建立项目(Win32 ...
- hbase 2.0.2 分布式安装配置/jar包替换
环境 zk: 3.4.10 hadoop 2.7.7 jdk8 hbase 2.0.2 三台已安装配置好的hadoop002,hadoop003,hadoop004 1.上传并解压hbase-2.1. ...
- Java与C#的语法区别
1.作用域 在java中 { { int a=1; } int a=2;//以上a作用域外的以下,再声明同名的变量,是允许的: } 在C#中,以上是不允许的[只要在同一个作用域内,以上或以下的代码中 ...