Code contracts are possible on concrete classes, abstract classes, interfaces and the generic versions of all the above. In my previous post I talked about how you can prove .NET uses code contracts internally, for example on the ICollection<T> interface. How did they add a contract to a generic interface then?
In order for a contract to be recognized on an interface, you need to take three steps:
- decorate the interface with a [ContractClass] attribute
- create a contract class and decorate it with a [ContractClassFor] attribute
- for any method or property that should have a contract: implement the interface explicitly.
So lets do that right now, but with a bit more fun:
Remember to enable runtime code contract checks in your project properties (right click poject, Properties.. then Code Contracts at the bottom)
We’re gonna make Fish, yes Fish.
public abstract class Fish
{
public abstract int EnergyValue { get; protected set; }
public void Consume()
{
this.EnergyValue = 0;
}
}
Well, not really fish, we’ll use politically correct fish so PETA (Gaia in Belgium) doesn’t come after us:
public class Tofu: Fish
{
private int _energyValue = 15;
public override int EnergyValue
{
get { return _energyValue; }
protected set { _energyValue = value; }
}
}
We’re thinking of having lots and lots of types of objects that will be consuming fish, so we’re creating an interface:
[ContractClass(typeof(FishEaterContract<T>))]
public interface IFishEater<T>
where T: Fish
{
void Eat(T fish);
}
Notice the attribute on top of the class. This won’t really compile, but we’ll fix that later.
Our interface needs a contract. For example, we don’t want to eat a fish twice right? That’d be just plain wrong towards the poor fish, and pretty gross!
[ContractClassFor(typeof(IFishEater<T>))]
public class FishEaterContract<T>: IFishEater<T>
where T: Fish
{
void IFishEater<T>.Eat(T fish)
{
Contract.Requires<ArgumentException>(fish.EnergyValue > 0);
}
}
Notice the attribute on top of the fish eater contract class, and how I explicitly implemented the interface (<type>.<method name>).
If you’d try to compile the source however, theres a few problems. As I’m sure you’d remember, .NET attributes do not support handing it generic type parameters. Oh no! But..Microsoft did it on ICollection<T>, am I saying they cheated? Notice I said generic type parameters. Not generic types. Noone said you had to specify the generic parameter:
[ContractClass(typeof(FishEaterContract<>))]
[ContractClassFor(typeof(IFishEater<>))]
Changing the attributes on your classes like this will make it compile. This is called a ‘non-instantiated generic type’.
So we’ve taken all three steps now and crossed your big compilation hurdle-- all that remains is an actual consumer of our politically correct fish:
public class EatsOnlyTofu: IFishEater<Tofu>
{
private int _energyLevel;
public void Eat(Tofu fish)
{
_energyLevel += fish.EnergyValue;
fish.Consume();
}
}
Finally, it’s time to run the program and try to consume the same piece of tofu twice:
Tofu tofu = new Tofu();
EatsOnlyTofu eater = new EatsOnlyTofu();
eater.Eat(tofu);
try
{
eater.Eat(tofu);
} catch (Exception e)
{
Console.WriteLine("I ate the same fish twice " +
"and ended up with a \""
+ e.GetType().Name + "\" instead.");
}
Console.WriteLine("press <enter>");
Console.ReadLine();
This yields the following output:
Phew, thanks to me you can now prevent yourself from eating the same fish twice in the future! Happy tofu-eating.