Code Contracts

Developer
Jul 26, 2014 at 7:21 PM
I'm experimenting with Code Contracts (not to be confused with data contracts), and getting pretty good results. That's why I want to replace all references to the Preconditions utility with methods from the System.Diagnostics.Contracts namespace.

One of the features that really appeal to me is static constraint checking. Instead of throwing an ArgumentException at runtime, you can make the code display a compiler error by specifying the preconditions in a code contract.

Example: checking for null

Old
void DoSomething(object arg)
{
    if (arg == null) throw new ArgumentNullException();
}
New
void DoSomething(object arg)
{
    Contract.Require(arg != null);
}
While they're semantically two different beasts, they both accomplish the same goal: the program will complain when the promise (arg != null) is broken.

The neat thing is that code contracts can be conditionally compiled. Made sure that your program never passes null values to DoSomething()? Then you don't need it to perform the additional null check, especially not for release builds. Code contracts makes this easy by compiling contracts only when the CONTRACTS_FULL symbol is defined.

To enable code analysis for code contracts, you do not have to reference any additional frameworks. You only have to install a VS extension that provides the actual contract analysis tools along with IntelliSense integration: http://visualstudiogallery.msdn.microsoft.com/1ec7db13-3363-46c9-851f-1ce455f66970

This information applies to everyone that uses the library in one way or another.

Configuring the extension is still somewhat of a black art. I'll set up a wiki page with screenshots once I understand it better.
Developer
Jul 27, 2014 at 6:50 PM
Edited Jul 27, 2014 at 6:52 PM
I found another really cool feature. You can create automatic properties that still support input validation. You can do that by specifying so-called class invariants.

As an example: an item chat link can represents item quantities from 1 to 255.
public class ItemChatLink : ChatLink
{
    /// <summary>Gets or sets the item identifier.</summary>
    public int ItemId { get; set; }
        
    /// <summary>Gets or sets an item quantity between 1 and 255, both inclusive.</summary>
    public int Quantity { get; set; }

    /// <summary>The invariant method for this class.</summary>
    [ContractInvariantMethod]
    private void ObjectInvariant()
    {
        Contract.Invariant(this.Quantity >= 1);
        Contract.Invariant(this.Quantity <= 255);
    }
}
Notice that we didn't have to put any code in the property setter. Easy, right?
Coordinator
Jul 28, 2014 at 6:57 PM
uhhhh nice features. I like them. I'll read up on code contracts later, they seem like a very handy feature.
Developer
Jul 31, 2014 at 4:07 PM
Edited Jul 31, 2014 at 7:38 PM
I implemented code contracts, but in the process removed code that explicitly throws exceptions for invalid input. Someone has to decide which methods should still crash in release builds when their contract is breached. Otherwise, the program will continue with invalid input, resulting in undefined behavior.

Official documentation: https://research.microsoft.com/en-us/projects/contracts/userdoc.pdf

Not every bit of information in that document applies to us.

To sum up what you need to know:
  • At compile-time:
    • Visual Studio may show warnings when "Perform Static Contract checking" is enabled
      • Useful for custom debug configurations only due to the time it takes to analyze the code.
  • At execution-time:
    • The program may throw a ContractException when "Perform Runtime Contract checking" is enabled and set to Full
      • Use Contract.Requires() where you want the code to crash.
      • Used in debug configurations only
    • The program may throw a custom exception when "Perform Runtime Contract checking" is disabled
      • Contract.Requires() are erased and the program is allowed to continue with invalid state.
      • Use legacy if-then-throw blocks where you want the code to crash.
      • Used in release configurations.
    • The program ignores contracts when "Perform Runtime Contract checking" is enabled and set to None
      • Contract.Requires() are erased and the program is allowed to continue with invalid state.
      • Legacy if-then-throw blocks are erased and the program is allowed to continue with invalid state.
      • Used for benchmarking tests only.
Because many features are only useful in debug builds, we have to figure something out for the NuGet package. For example: it is impossible for the static checker to analyze assemblies that were compiled in a Release configuration. We may offer a separate development build that includes contracts, but it probably should not be distributed via NuGet.

Please let me know if I made any mistakes. I'm still learning.

edit

Does NuGet support distributing multiple DLLs per package? It is possible to compile contracts as an optional assembly that is separate from the main library. That way, it's an opt-in kind of deal: the user can enable "Call-site Requires Checking", but doesn't have to. This enables things like static checking even though the library was compiled in a Release configuration.

edit

Pretty good article: http://mediocresoft.com/things/code-contracts-from-new-project-to-nuget#nuget

edit

I'm now restoring if-then-throw blocks in places where it would hurt the most if you'd let the program continue with invalid input.
Coordinator
Aug 1, 2014 at 12:14 PM
Maybe we could use preprocessor directives to check if we are in debug or release mode.
Does NuGet support distributing multiple DLLs per package?
You can put as many dlls in one package as you want, there would be no problem to make a separate for code contracts.
Developer
Aug 1, 2014 at 1:32 PM
No need to insert preprocessor directives. The contracts extension rewrites the IL so that it does or doesn't validate input depending on how it was configured. You can have multiple configurations by creating a new configuration based on the Debug or Release configuration, then tweaking it to your specific needs. This is somewhat complicated, but we're not forcing anybody.