Sorry if this question seems too long. Before I can ask it, I need to show where it's coming from.
Set-up:
Given the following immutable type Rectangle
:
class Rectangle
{
public Rectangle(double width, double height) { … }
public double Width { get { … } }
public double Height { get { … } }
}
… it seems perfectly legal to derive a type Square
from it:
using System.Diagnostics.Contracts;
class Square : Rectangle
{
public Square(double sideLength) : base(sideLength, sideLength) { }
[ContractInvariantMethod]
void WidthAndHeightAreAlwaysEqual()
{
Contract.Invariant(Width == Height);
}
}
… because the derived class can make sure that its own invariant isn't ever violated.
But as soon as I make Rectangle
mutable:
class Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
…
}
… I should no longer derive Square
from it, because Square
shouldn't ever have independent setters for Width
and Height
.
Question:
What can I do with Code Contracts such that it will warn me of a contract violation as soon as I derive Square
from the mutable Rectangle
class? Preferably, Code Contracts' static analysis would already give me a warning at compile-time.
In other words, my goal is to encode the following rules with Code Contracts:
Width
andHeight
of aRectangle
may be changed independently from one another.Width
andHeight
of aSquare
cannot be changed independently from one another, and that wouldn't be meaningful in the first place.
… and do it in such a way that Code Contracts will notice whenever these rules "collide".
What I've considered so far:
1. Adding an invariant to Rectangle
:
class Rectangle
{
…
[ContractInvariantMethod]
void WidthAndHeightAreIndependentFromOneAnother()
{
Contract.Invariant(Width != Height || Width == Height);
}
}
The problem with this approach is that while the invariant correctly states, "Width and Height don't have to be equal, but they can be", it is ineffectual, (1) because it is a tautology, and (2) because it is less restrictive than the invariant Width == Height
in the derived class Square
. Perhaps it's even optimised away by the compiler before Code Contracts ever sees it.
2. Adding post-conditions to Rectangle
's setters:
public double Width
{
get { … }
set { Contract.Ensures(Height == Contract.OldValue(Height)); … }
}
public double Height
{
get { … }
set { Contract.Ensures(Width == Contract.OldValue(Width)); … }
}
This will forbid the derived class Square
from simply updating Height
to Width
whenever Width
is changed and vice versa, it won't stop me per se from deriving a Square
class from Rectangle
. But that's my goal: getting Code Contracts to warn me that Square
must not be derived from a mutable Rectangle
.