c#Code Contracts代码协定

Code Contracts的命名空间:System.Diagnostics.Contracts

集合1.

安装Code Contracts for .NET插件
Contracts.devlab9ts.msi

安装了该插件后,可以在项目的属性页上发现多了一个Code Contracts的标签:

勾上"Perform Runtime Check"选项,只是可以看到右侧的下拉框有五个选项,这里分别介绍一下它们的区别:

  1. Full表示执行所有的代码协定语句。
  2. Pre and Post表示执行前置和后置条件检查,即Contract.Require和Contract.Ensures。
  3. Preconditions 表示只执行前置条件检查,即Contract.Require。
  4. ReleaseRequires 表示执行public类的public方法的前置条件检查。
  5. 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

 

posted @ 2017-07-11 16:10  阿文sky  阅读(3497)  评论(0编辑  收藏  举报