Извините, если этот вопрос кажется слишком длинным. Прежде чем я смогу спросить об этом, мне нужно показать, откуда оно.
Настройка:
Имеется следующий неизменяемый тип Rectangle
:
class Rectangle
{
public Rectangle(double width, double height) { … }
public double Width { get { … } }
public double Height { get { … } }
}
& hellip; кажется вполне законным извлечь из него тип Square
:
using System.Diagnostics.Contracts;
class Square : Rectangle
{
public Square(double sideLength) : base(sideLength, sideLength) { }
[ContractInvariantMethod]
void WidthAndHeightAreAlwaysEqual()
{
Contract.Invariant(Width == Height);
}
}
& hellip; потому что производный класс может убедиться, что его собственный инвариант никогда не нарушается.
Но как только я сделаю Rectangle
изменяемым:
class Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
…
}
& hellip; Я больше не должен получать Square
из него, потому что Square
никогда не должен иметь независимых сеттеров для Width
и Height
.
Вопрос:
Что я могу сделать с Code Contracts, чтобы он предупреждал меня о нарушении контракта, как только я выведу Square
из изменяемого класса Rectangle
? Предпочтительно, статический анализ Code Contracts уже дал бы мне предупреждение во время компиляции.
Другими словами, моя цель - кодировать следующие правила с помощью контрактов кода:
Width
и Height
для Rectangle
могут быть изменены независимо друг от друга.
Width
и Height
для Square
не могут быть изменены независимо друг от друга, и это не имело бы смысла в первую очередь.
& hellip; и делать это таким образом, чтобы Кодовые контракты обращали внимание, когда эти правила «сталкиваются».
То, что я до сих пор считал:
1. Добавление инварианта в Rectangle
:
class Rectangle
{
…
[ContractInvariantMethod]
void WidthAndHeightAreIndependentFromOneAnother()
{
Contract.Invariant(Width != Height || Width == Height);
}
}
Проблема с этим подходом состоит в том, что хотя инвариант правильно заявляет: «Ширина и высота не должны быть равными, но они могут быть одинаковыми», это неэффективно, (1) потому что это тавтология, и (2 ) потому что он менее строг, чем инвариант Width == Height
в производном классе Square
. Возможно, он даже оптимизирован компилятором до того, как Code Contracts его увидит.
2. Добавление постусловий к сеттерам Rectangle
:
public double Width
{
get { … }
set { Contract.Ensures(Height == Contract.OldValue(Height)); … }
}
public double Height
{
get { … }
set { Contract.Ensures(Width == Contract.OldValue(Width)); … }
}
Это запретит производному классу Square
просто обновлять Height
до Width
всякий раз, когда изменяется Width
, и наоборот, это не остановит меня per se от получения Square
класс от Rectangle
. Но это моя цель: заставить Code Contracts предупредить меня, что Square
не должен быть производным от изменяемого Rectangle
.