4.14. Code Contracts

Code contracts are a method for expressing constraints and assumptions within your code. They allow specification of complex rules that can be validated at both compile time and runtime. Code contracts are also supported in VS2008.

NOTE

Compile-time or static verification is available only in the Premium and Ultimate editions of Visual Studio. This is a real shame because it will probably prevent widespread adoption of this great technology rather than encouraging users to purchase a more expensive edition of Visual Studio (similar to MSTest and VS2005?). Hopefully this will not be permanent, and you will see static verification available in all future versions of Visual Studio.

In addition to providing validation, code contracts can assist with code documentation and aiding understanding of a problem. Functionality is available to automatically remove contracts from production code and separate them into a separate assembly if third parties want to use them. Code contracts are part of Microsoft's ongoing research project Spec#; Spec#'s developers say they have been influenced by the Eiffel, JML, and AsmL languages.

Code contracts are still in active development, so this functionality might change.


4.14.1. Hello Code Contracts

To ensure that values are not null, you have probably written code similar to the following many times:

public void myFunction(string input)
{
    if (input == null) throw new System.ArgumentNullException("Input cannot be null");
}

Or perhaps you have utilized the debug or trace assert like so:

Debug.Assert(input != null);

Code contracts are superior to the previous methods because they do the following:

  • Allow the creation of more complex rules

  • Can help you write better code by getting you to think about constraints within your code

  • Can reduce/prevent side effects

  • Can be validated at both compile time and runtime

  • Are easy to read

  • Can be interpreted by automated tools such as PEX (http://research.microsoft.com/en-us/projects/Pex)

  • Work with XML documentation generation

  • Unlike debug statements, can optionally be utilized in both debug and release builds

  • Can be separated into separate assemblies for third-party use

In the "Example Code Contracts" section, which follows, we'll create a simple code contract to ensure that an input value is not null or equal to 5.

4.14.2. Installing Code Contracts

Although VS2010 Professional edition contains some of the assemblies required for code contracts, the team didn't want to tie code contract development to the release of Visual Studio, so the Code Contract SDK is available as a separate download.

You can run the following code without downloading the SDK, but the code contracts won't actually do anything. Make sure to download the SDK first.


There are two versions of the SDK available (currently named Standard edition and TFS edition). The TFS edition is for Premium/Ultimate and contains compile-time verification and some additional features. The Standard edition does not contain this full static verification, but will offer warnings if contracts are breached (in my experiments with invariants and pure methods).

SDKs are available here: http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx.

4.14.3. Example Code Contract

Once you have installed the Code Contract SDK, create a new console application and then add the following using directive:

using System.Diagnostics.Contracts;

Now add the following code:

static void Main(string[] args)
{
    DoSomething(5);
}

public static void DoSomething(int? Input)
{
    Contract.Requires(input != null);
    Contract.Requires(input != 5);
}

Before you run the application, go to the Properties page of your project and select the Code Contracts tab (see Figure 4-4).

Figure 4.4. Code Contracts tab

Check the box marked Perform Runtime Contract Checking and run the code. You should receive an error message similar to that shown in Figure 4-5.

Figure 4.5. Alert box showing failure of code contract assumption

4.14.4. Enabling Code Contract Static Verification (Premium/Ultimate Edition Only)

To enable static verification, you need to go into the project Properties screen, select the Code Contract tab, and ensure that the Static Checking option is selected. When you compile your applications now, you should see that the contracts are checked at compile time.

You might ask why static verification is not always on. One reason is that if you are writing unit tests, you might want to pass null values into your methods and ensure that your code handles them correctly. If static verification were always on, you could not run your unit tests.

4.14.5. Contract Inheritance

You should note that contracts are inherited between classes, but it is not possible to add additional preconditions.

4.14.6. Architecture

Behind the scenes, code contracts rewrite the generated IL. At a high level, you can divide code contract architecture into three main components:

  • Static method: Expresses assumptions and constraints

  • Binary rewriter: Performs runtime checks

  • Static checker: Verifies assumptions at compile time (TFS edition only)

Let's now look at some of the different ways to declare assumptions in the code using the static methods available in code contracts.

4.14.7. Conditions

Code contracts allows you to create three types of conditions:

  • Preconditions

  • Postconditions

  • Invariants

The following sections discuss some of the conditions you might want to utilize (this is by no means an exhaustive list and is being added to in each release).

4.14.7.1. Preconditions

Preconditions must be true at the start of a method.

4.14.7.1.1. Contract.Assert()

In debug build, Contract.Assert() ensures that a condition is true.

Contract.Assert(input != null);

4.14.7.1.2. Contract.Assume()

Contract.Assume() is used and tells code analysis tools to assume that a condition is true (e.g., if you are calling a method you have written and you are sure it will never return a null result):

Contract.Assume(input != null);

4.14.7.1.3. Contract.Requires()

Contract.Requires() ensures that a condition is true before subsequent code is run. The following will ensure that the input parameter is not null:

Contract.Requires(input != null);

Contract.Requires() has an overload that allows you to specify an exception type and message to be thrown:

Contract.Requires<ArgumentNullException>(input != null, "input");

4.14.7.1.4. Contract.EndContractBlock()

The Contract.EndContractBlock() statement tells the compiler to treat code as a precondition and allows you to utilize legacy code without converting it to code contracts format:

if (input==null) throw new System.ArgumentNullException("input is null");
Contract.EndContractBlock();

NOTE

You cannot use EndContractBlock in conjunction with any other preconditions.

4.14.7.2. Postconditions

Postconditions are conditions that are true at the exit of your method calls.

4.14.7.2.1. Contract.Ensures()

Contract.Ensures() ensures that a condition is true at the exit of your method:

Contract.Ensures(output != 7);

4.14.7.2.2. Contract.EnsuresOnThrow()

Contract.EnsuresOnThrow() ensures that a specific exception type is thrown for a condition:

Contract.EnsuresOnThrow<System.IO.IOException>(input != null);

4.14.7.2.3. Contract.ForAll()

Contract.ForAll() allows the iteration through a set to ensure that all members meet a specific condition:

Contract.ForAll(MySet, i=> i!=null);

4.14.7.3. Object Invariants

Object invariants allow you to specify conditions that must always be true for an object, and are created by decorating a procedure with the [ContractInvariantMethod] attribute. The following code ensures that the ImportantData variable can never be null:

[ContractInvariantMethod]
void MyInvariant() {
    Contract.Invariant(ImportantData !=null);
}

4.14.8. Code Contract Values

Code contracts offer some pseudovariables that can be useful when writing your conditions.

4.14.8.1. Contract.Result()

Contract.Result()accesses a value in a condition that will be returned from a function without referring to it directly:

Contract.Ensures(Contract.Result<Int32?>() >= −1);

4.14.8.2. Contract.OldValue()

Contract.OldValue()represents the values state at the start of the method call. OldValue() performs a shallow copy of the specified variable and can be used to see whether a value has changed:

Contract.Ensures(input != Contract.OldValue(Input));

4.14.9. Pure

Methods that are called within a contract should be decorated with the attribute [Pure], which indicates that the method has no side effects (doesn't alter the state of any other objects). If you don't add this attribute, you will receive a warning similar to this:

Detected call to impure method
'ConsoleApplication6.Program.Multiply(System.Int32,System.Int32)' in a pure region in method

To mark a method as pure, simply add the [Pure] attribute as follows:

[Pure]
public static double Multiply(int x, int y)
{
    return x * y;
}

4.14.10. Interface Contracts

Because interfaces cannot have method bodies, contracts must be declared in a slightly different way:

[ContractClass(typeof(ContractForInteface))]
interface IDoSomething
{
    int DoSomething(int value);
}

[ContractClassFor(typeof(IDoSomething))]
sealed class ContractForInteface : IDoSomething
{
    int IDoSomething.DoSomething(int value)
    {
        Contract.Requires( value != 0);

        //contracts require a dummy value
        return default(int);
    }

4.14.11. PEX

Developers interested in the code contracts features might also be interested in the PEX research project. PEX aims to automate testing by analyzing code. For more information, please refer to http://research.microsoft.com/en-us/projects/Pex.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.11.227