*** NOTE *** This post has been a long time coming, I would like to thank Kevin Hazzard for doing a kick ass Contracts session at TriNug and reminding me to get off my ass and blog about this great feature *** NOTE ***
If you are not familiar with the Code Contracts library which is coming out of Microsoft R&D labs, you need to check this pretty cool little library out. As of Vs2010/.Net 4.0 this library will be making the jump out of the R&D labs.
Over the next few blog posts we will be taking a look at the topics below.
What is a Interface Contract?
This part of the contracts library allows you to provide a ‘buddy class’ (as Kevin called it) which tags along with some interface in your application and provides contract validation for your interface. Below is the definition per the contracts user guide:
Since most languages/compilers (including C# and VB) will not let you put method bodies within an interface, writing contracts for interface methods requires creating a separate contract class to hold them. The interface and its contract class are linked via a pair of attributes.
Imagine you have the following interface as defined below
public interface IPayrollCalculator
{
double CalculatePaycheck( double baseRate, int hoursWorked );
double DetermineBaseRate( EmployeeType employeeType );
}
With this interface assume you have the following 2 implementation classes, each which utilized various contracts to ensure quality.
public class HourlyPayrollCalculator : IPayrollCalculator {
public double CalculatePaycheck( double baseRate, int hoursWorked )
{
Contract.Requires( baseRate > 0 );
Contract.Requires( hoursWorked > 0 );
Contract.Ensures( Contract.Result() > 0 );
Contract.Requires( hoursWorked <= 50 );
return baseRate * hoursWorked;
}
public double DetermineBaseRate( EmployeeType employeeType )
{
Contract.Ensures( Contract.Result() > 0 );
var baseRate = 0d;
switch ( employeeType )
{
case EmployeeType.LazyJoe:
baseRate = 7.5;
break;
case EmployeeType.NineToFiver:
baseRate = 9.5;
break;
case EmployeeType.GoGetter:
baseRate = 12.5;
break;
case EmployeeType.OverAchiever:
baseRate = 17.5;
break;
}
return baseRate;
}
}
public class SalaryPayrollCalculator : IPayrollCalculator
{
public double CalculatePaycheck( double baseRate, int hoursWorked )
{
Contract.Requires( baseRate > 0 );
Contract.Requires( hoursWorked > 0 );
Contract.Ensures( Contract.Result() > 0 );
Contract.Requires( hoursWorked <= 60 );
return baseRate * hoursWorked;
}
public double DetermineBaseRate( EmployeeType employeeType )
{
Contract.Ensures( Contract.Result() > 0 );
var baseRate = 0d;
switch ( employeeType )
{
case EmployeeType.LazyJoe:
baseRate = 17.5;
break;
case EmployeeType.NineToFiver:
baseRate = 19.5;
break;
case EmployeeType.GoGetter:
baseRate = 22.5;
break;
case EmployeeType.OverAchiever:
baseRate = 27.5;
break;
}
return baseRate;
}
}
One thing should immediately jump out at you from looking at the above implementation classes, we have redundant logic in each of our classes. This is where the beauty of Interface Contracts come into play. I am able to create a stand alone class which can hold onto my duplicated contracts logic and use this class for validation. To do this I need to create the class below;
[ContractClassFor(typeof(IPayrollCalculator))]
public sealed class IPayrollCalculatorContract : IPayrollCalculator
{
double IPayrollCalculator.CalculatePaycheck( double baseRate, int hoursWorked )
{
Contract.Requires( baseRate > 0 );
Contract.Requires( hoursWorked > 0 );
Contract.Ensures( Contract.Result() > 0 );
return default( double );
}
double IPayrollCalculator.DetermineBaseRate( EmployeeType employeeType )
{
Contract.Ensures( Contract.Result() > 0 );
return default( double );
}
}
When looking at the class above there are a few things that need to be pointed out
- There is no need to provide a ‘real return’ value if you have a non-void method. You simply can return some default value. The reason for this is that the IL re-writer is not going to keep your code as is, so no need to provide anything.
- You will want to explicitly reference your interface methods (ie IPayrollCalcualtor.CalculatePaycheck) and leave the methods private. If you do not do this you will not get the behavior you expect.
- You need to decorate your class with the ContractClassFor attribute and this MUST point to the interface class which the contract applies to.
Now that we have defined our ‘buddy class’ which holds onto our interface contracts we need to modify our original interface in order to link it to the contract implementation. To do this take a look at the modified interface below:
[ContractClass(typeof(IPayrollCalculatorContract))]
public interface IPayrollCalculator
{
double CalculatePaycheck( double baseRate, int hoursWorked );
double DetermineBaseRate( EmployeeType employeeType );
}
Notice the only difference in the code above from the original code is the introduction of the ContractClass attribute. This attribute creates a circular dependency to the implementation class for the contracts. If you do not have this setup your contracts will not work as expected.
Now that I have setup my interface contract I can remove the redundant contracts from my 2 classes, the code below shows this in action.
public class HourlyPayrollCalculator : IPayrollCalculator {
public double CalculatePaycheck( double baseRate, int hoursWorked )
{
Contract.Requires( hoursWorked <= 50 );
return baseRate * hoursWorked;
}
public double DetermineBaseRate( EmployeeType employeeType )
{
var baseRate = 0d;
switch ( employeeType )
{
case EmployeeType.LazyJoe:
baseRate = 7.5;
break;
case EmployeeType.NineToFiver:
baseRate = 9.5;
break;
case EmployeeType.GoGetter:
baseRate = 12.5;
break;
case EmployeeType.OverAchiever:
baseRate = 17.5;
break;
}
return baseRate;
}
}
public class SalaryPayrollCalculator : IPayrollCalculator
{
public double CalculatePaycheck( double baseRate, int hoursWorked )
{
Contract.Requires( hoursWorked <= 60 );
return baseRate * hoursWorked;
}
public double DetermineBaseRate( EmployeeType employeeType )
{
var baseRate = 0d;
switch ( employeeType )
{
case EmployeeType.LazyJoe:
baseRate = 17.5;
break;
case EmployeeType.NineToFiver:
baseRate = 19.5;
break;
case EmployeeType.GoGetter:
baseRate = 22.5;
break;
case EmployeeType.OverAchiever:
baseRate = 27.5;
break;
}
return baseRate;
}
}
As you can see Interface Contracts can allow for reduced redundancy and increased clarity. Hope this helps.
Till next time,
Posted
05-14-2010 5:41 AM
by
Derik Whittaker