I recently noticed an — at least for me — unexpected behaviour when working with Code Contracts in interfaces. When you define a contract for an interface (via ContractClassFor) and implement a class from that interface that contract is certainly enforced. However when you define additional requirements on that implementation class these contracts are evaluated BEFORE the interface contracts.
Here is a quick example to demonstrate the behaviour:
namespace biz.dfch.CS.Examples.CodeContracts
{
[ContractClass(typeof(ContractClassForIInterfaceWithContract))]
public interface IInterfaceWithContract
{
void Funcenstein(string parameter);
}
[ContractClassFor(typeof(IInterfaceWithContract))]
abstract class ContractClassForIInterfaceWithContract : IInterfaceWithContract
{
public void Funcenstein(string parameter)
{
Contract.Requires(null != parameter);
}
}
[TestClass]
public class IInterfaceWithContractTest
{
class InterfaceWithContractImpl : IInterfaceWithContract
{
public void Funcenstein(string parameter)
{
// more restrictive requirement than on interface
Contract.Requires(parameter.StartsWith("edgar"));
return;
}
}
[TestMethod]
[ExpectContractFailure]
public void CallingFuncensteinViolatesClassCodeContract()
{
// Arrange
var parameter = "arbitrary-string-other-than-edgar";
// Act
var sut = new InterfaceWithContractImpl();
sut.Funcenstein();
}
}
}
We define an interface IInterfaceWithContract that does not accept null strings on its Funcenstein method. The actual class implementation InterfaceWithContractImpl now requires a string that must StartWith edgar (which is more restrictive). Now we run into a problem, that the class implementation is evaluated first, which means we possibly de-reference a null pointer which would normally lead to getting the dreaded object reference not set to an instance of an object (but which is encapsulated by the __ContractsRuntime+ContractException exception.
If we had checked the contract from the interface first we would have gotten a more meaningful message (indicating that the whole string is actually null). Now we just get the message that the string does not start with edgar.
>> Test method biz.dfch.CS.Examples.CodeContracts.IInterfaceWithContractTest.
>> CallingFuncensteinViolatesClassCodeContract threw exception:
>> System.Diagnostics.Contracts.__ContractsRuntime+ContractException:
>> Precondition failed: parameter.StartsWith(“edgar”)
Just something to keep in mind when troubleshooting.
