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.
|
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.
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.
|
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.
Once you have installed the Code Contract SDK, create a new console application and then add the following using directive:
using System.Diagnostics.Contracts;
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).
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.
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.
You should note that contracts are inherited between classes, but it is not possible to add additional preconditions.
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.
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).
Preconditions must be true at the start of a method.
In debug build, Contract.Assert() ensures that a condition is true.
Contract.Assert(input != null);
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);
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");
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.
Postconditions are conditions that are true at the exit of your method calls.
Contract.Ensures() ensures that a condition is true at the exit of your method:
Contract.Ensures(output != 7);
Contract.EnsuresOnThrow() ensures that a specific exception type is thrown for a condition:
Contract.EnsuresOnThrow<System.IO.IOException>(input != null);
Contract.ForAll() allows the iteration through a set to ensure that all members meet a specific condition:
Contract.ForAll(MySet, i=> i!=null);
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); }
Code contracts offer some pseudovariables that can be useful when writing your conditions.
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);
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));
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; }
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); }
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.
18.217.80.88