Sometimes we need a way to make different implementations of same interface follow same rules. One option is to duplicate contracts to all implementation but this is not good option because we have duplicated code then. The other option is to force contracts to all implementations at interface level. In this posting I will show you how to do it using interface contracts and contracts class.
- 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
Using code from previous example about unit testing code with code contracts I will go further and force contracts at interface level. Here is the code from previous example. Take a careful look at it because I will talk about some modifications to this code soon.
public interface IRandomGenerator
{
int Next(int min, int max);
}
public class RandomGenerator : IRandomGenerator
{
private Random _random = new Random();
public int Next(int min, int max)
{
return _random.Next(min, max);
}
}
public class Randomizer
{
private IRandomGenerator _generator;
private Randomizer()
{
_generator = new RandomGenerator();
}
public Randomizer(IRandomGenerator generator)
{
_generator = generator;
}
public int GetRandomFromRangeContracted(int min, int max)
{
Contract.Requires<ArgumentOutOfRangeException>(
min < max,
"Min must be less than max"
);
Contract.Ensures(
Contract.Result<int>() >= min &&
Contract.Result<int>() <= max,
"Return value is out of range"
);
return _generator.Next(min, max);
}
}
If we look at the GetRandomFromRangeContracted() method we can see that contracts set in this method are applicable to all implementations of IRandomGenerator interface. Although we can write new implementations as we want these implementations need exactly the same contracts. If we are using generators somewhere else then code contracts are not with them anymore.
To solve the problem we will force code contracts at interface level.
NB! To make the following code work you must enable Contract Reference Assembly building from project settings.
Interface contracts and contracts class
Interface contains no code – only definitions of members that implementing type must have. But code contracts must be defined in body of member they are part of. To get over this limitation, code contracts are defined in separate contracts class. Interface is bound to this class by special attribute and contracts class refers to interface through special attribute.
Here is the IRandomGenerator with contracts and contracts class. Also I write simple fake so we can test contracts easily based only on interface mock.
[ContractClass(typeof(RandomGeneratorContracts))]
public interface IRandomGenerator
{
int Next(int min, int max);
}
[ContractClassFor(typeof(IRandomGenerator))]
internal sealed class RandomGeneratorContracts : IRandomGenerator
{
int IRandomGenerator.Next(int min, int max)
{
Contract.Requires<ArgumentOutOfRangeException>(
min < max,
"Min must be less than max"
);
Contract.Ensures(
Contract.Result<int>() >= min &&
Contract.Result<int>() <= max,
"Return value is out of range"
);
return default(int);
}
}
public class RandomFake : IRandomGenerator
{
private int _testValue;
public RandomGen(int testValue)
{
_testValue = testValue;
}
public int Next(int min, int max)
{
return _testValue;
}
}
To try out these changes use the following code.
var gen = new RandomFake(3);
try
{
gen.Next(10, 1);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
try
{
gen.Next(5, 10);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
Now we can force code contracts to all types that implement our IRandomGenerator interface and we must test only the interface to make sure that contracts are defined correctly.
View Comments (1)
How can you deal with Properties defined in the interface?