In my last posting about code contracts I introduced you how to force code contracts to classes through interfaces. In this posting I will go step further and I will show you how code contracts work in the case of inherited classes.
- Controlling randomizer using code contracts
- Using runtime checking of code contracts in Visual Studio 2010
- Code Contracts: Hiding ContractException
- Code Contracts: Unit testing contracted code
- Forcing code contracts through interface contracts
- Invariant code contracts – using class-wide contracts
- Code contracts and inheritance
- Enabling XML-documentation for code contracts
- Using Sandcastle to build code contracts documentation
- Code Contracts: How they look after compiling?
- Code Contracts: validating arrays and collections
As a first thing let’s take a look at my interface and code contracts.
[ContractClass(typeof(ProductContracts))]
public interface IProduct
{
int Id { get; set; }
string Name { get; set; }
decimal Weight { get; set; }
decimal Price { get; set; }
}
[ContractClassFor(typeof(IProduct))]
internal sealed class ProductContracts : IProduct
{
private ProductContracts() { }
int IProduct.Id
{
get
{
return default(int);
}
set
{
Contract.Requires(value > 0);
}
}
string IProduct.Name
{
get
{
return default(string);
}
set
{
Contract.Requires(!string.IsNullOrWhiteSpace(value));
Contract.Requires(value.Length <= 25);
}
}
decimal IProduct.Weight
{
get
{
return default(decimal);
}
set
{
Contract.Requires(value > 3);
Contract.Requires(value < 100);
}
}
decimal IProduct.Price
{
get
{
return default(decimal);
}
set
{
Contract.Requires(value > 0);
Contract.Requires(value < 100);
}
}
}
And here is the product class that inherits IProduct interface.
public class Product : IProduct
{
public int Id { get; set; }
public string Name { get; set; }
public virtual decimal Weight { get; set; }
public decimal Price { get; set; }
}
if we run this code and violate the code contract set to Id we will get ContractException.
public class Program
{
static void Main(string[] args)
{
var product = new Product();
product.Id = -100;
}
}
Now let’s make Product to be abstract class and let’s define new class called Food that adds one more contract to Weight property.
public class Food : Product
{
public override decimal Weight
{
get
{
return base.Weight;
}
set
{
Contract.Requires(value > 1);
Contract.Requires(value < 10);
base.Weight = value;
}
}
}
Now we should have the following rules at place for Food:
- weight must be greater than 1,
- weight must be greater than 3,
- weight must be less than 100,
- weight must be less than 10.
Interesting part is what happens when we try to violate the lower and upper limits of Food weight. To see what happens let’s try to violate rules #2 and #4. Just comment one of the last lines out in the following method to test another assignment.
public class Program
{
static void Main(string[] args)
{
var food = new Food();
food.Weight = 12;
food.Weight = 2;
}
}
And here are the results as pictures to see where exceptions are thrown.
As you can see for both violations we get ContractException like expected.
Code contracts inheritance is powerful and at same time dangerous feature. Although you can always narrow down the conditions that come from more general classes it is possible to define impossible or conflicting contracts at different points in inheritance hierarchy.
View Comments (4)
Very nice! I'm curious, what's the performance cost of a contract vs non-contract? I know you don't get something for nothing, but I was curious just how "heavy" it is.
Thanks for question James!
I plan to make some performance measuring during next couple of days. So, stay tuned :)
why would we use class code contracts vs data annotation attributes?
Isn´t it a violation of the Liskov substitution principle, when you are tightening the preconditions from w < 100 to w < 10? It seems to me like something the code contract framework should catch and disallow. I heard from Mike Barnett, that Code Contracts does not support the loosening of preconditions in derived classes, which seem even more weird if they allow them to be tightened.