很抱歉,这个问题似乎太长了。在我问之前,我需要展示它的来源。
给出以下不可变类型Rectangle
:
class Rectangle
{
public Rectangle(double width, double height) { … }
public double Width { get { … } }
public double Height { get { … } }
}
...从中导出类型Square
似乎是完全合法的:
using System.Diagnostics.Contracts;
class Square : Rectangle
{
public Square(double sideLength) : base(sideLength, sideLength) { }
[ContractInvariantMethod]
void WidthAndHeightAreAlwaysEqual()
{
Contract.Invariant(Width == Height);
}
}
...因为派生类可以确保不会违反自己的不变量。
但是,只要我Rectangle
可变:
class Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
…
}
...我不应再从中获取Square
,因为Square
不应该为Width
和Height
设置独立的设置器。
我可以对代码合同做些什么,一旦我从可变Square
类派生Rectangle
,它就会警告我合同违规?最好是,Code Contracts的静态分析在编译时会给我一个警告。
换句话说,我的目标是使用代码合同对以下规则进行编码:
Width
和Height
的{{1}}可能会彼此独立更改。Rectangle
和Width
的{{1}}不能彼此独立更改,而且首先没有意义。
...并且只要这些规则“碰撞”,代码合约就会注意到这一点。
1。向Height
添加不变量:
Square
这种方法的问题在于,虽然不变量正确地指出,“宽度和高度不必相等,但它们可以是”,它是无效的,(1)因为它是重言式,并且(2) )因为它比派生类Rectangle
中的不变class Rectangle
{
…
[ContractInvariantMethod]
void WidthAndHeightAreIndependentFromOneAnother()
{
Contract.Invariant(Width != Height || Width == Height);
}
}
限制性更小。也许它甚至在Code Contracts看到它之前就被编译器优化了。
2。将后置条件添加到Width == Height
的二传手:
Square
这将禁止派生类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
,反之亦然,它不会阻止我 per se 从Width
派生Width
类。但这是我的目标:获取代码合同以警告我Square
不得来自可变Rectangle
。
答案 0 :(得分:4)
最近MSDN杂志上有一篇非常相关的文章讨论了基本相同的问题:"Code Contracts: Inheritance and the Liskov Principle" - 我无法找到更好的答案。
您认为“非法”子类型基本上违反了Liskov替换原则,文章显示了代码合同如何帮助检测此问题。
答案 1 :(得分:2)
July 2011 issue of MSDN Magazine中有一篇文章用相同的例子描述了你的问题,并讨论了代码契约的使用和Liskov替换原则。
答案 2 :(得分:0)
感谢@BrokenGlass和@Mathias链接到最近的MSDN杂志文章Code Contracts: Inheritance and the Liskov Principle。虽然我认为它有些不对劲(我会在一秒钟内完成...看到这个答案的第二部分),这有助于我决定解决方案。
我将以下后置条件添加到基类Rectangle
:
public double Width
{
set { Contract.Ensures(Height == Contract.OldValue(Height)); }
}
public double Height
{
set { Contract.Ensures(Width == Contract.OldValue(Width)); }
}
这些基本断言Width
和Height
可以彼此独立设置。
在派生类Square
中添加了一个不变量:
Contract.Invariant(Width == Height);
这基本上恰恰相反。
总而言之,这些最终会导致合同违规 - 诚然不是在编译时,但似乎只能在某些极端情况下使用代码合同(即派生时)课程开始添加前提条件。)
MSDN文章似乎显示了一个具有编译时警告优势的解决方案。为什么我坚持上面没有这个奖金的解决方案?
简答:
代码示例中无法解释的变化清楚地表明文章设置了一个人为的情况,以展示Code Contracts的静态分析。
答案很长:
本文首先介绍Rectangle
类的代码示例(→图1),其中包含Width
和Height
的各个setter。下次显示Rectangle
类时(→图2),设置者已设为private
,仅通过添加的方法SetSize(width, height)
使用。
文章没有解释为什么这种改变是默默引入的。事实上,在Rectangle
单独的情况下,这种变化可能根本没有任何意义,除非你已经知道你会得到像Square
这样的类,你需要添加一个前条件width == height
。当Width
和Height
的设置者彼此分开时,您无法将此作为前置条件添加。如果您无法添加该前提条件,则不会从代码合同中获得编译时警告。