Richard Tallent’s occasional blog

Contracts are a poor substitute for strong typing

I was reading about some planned C# improvements on the Roslyn forum and decided to cross-post my thoughts here that I made in a comment on a thread that included a discussion about code contracts. The comment follows:

Code contracts would be an improvement, but IMHO they treat the symptom, not the disease, as it were.

Say your method only accepts whole numbers, but uses an `int` parameter, which can theoretically be negative. Sure, putting a contract on the parameter to ensure it is >= 0 provides some protection, but it just moves the problem, so now the callee either has to do the same checks or defaulting before calling the method, or it has to handle the exceptions.

In the end, code contracts don’t address the underlying issue — your parameter has the wrong logical type. You’re using `int` when you really want a far more limited type of either _zero or a positive integer._ Using an unsigned integer would be better, but has its own foibles, so we hold our nose and pretend that a signed type for a never-negative parameter is our best option.

So what’s really going on is that your code contract is creating, logically, an anonymous type with restrictions not present in the base type. But by making it anonymous, it’s not reusable, either between methods or between the method and the caller.

A better approach, IMHO, is to use the type system. E.g., if I want only whole numbers, I create a type that aliases int but does not allow negative numbers to be assigned to it:

public contract WholeInt : int where >= 0;
public contract PositiveInt : int where >= 1 ?? 1;
public contract NNString : string where !null ?? String.Empty;

public class Customer {
  public WholeInt Age { get; set; }
  public NNString Name { get; set; }
  public PositiveInt NumVisits { get; set; }

This moves the responsibility back where it belongs: where the invalid value assignment occurs, not when some hapless method gets the bad value as an argument. It also encourages reuse, and provides the option of an _alternative default_ rather than an exception when a bad value is assigned or the default value of the underlying type would be invalid.

To allow better backward compatibility, it should always be possible to implicitly cast one of these contract types to their underlying type. This would allow, for example, `ICollection.Count` to return a `WholeInt` but callers can still assign the result directly to Int32.

Using the keyword `contract` above is just an example — perhaps extending the concept of an Interface would be better.


comments powered by Disqus