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.