C# 我是否可以获得代码合同来警告我;“非法”;子类型?
抱歉,如果这个问题太长。在我问之前,我需要说明它是从哪里来的 设立: 给定以下不可变类型C# 我是否可以获得代码合同来警告我;“非法”;子类型?,c#,compiler-warnings,code-contracts,invariants,subtyping,C#,Compiler Warnings,Code Contracts,Invariants,Subtyping,抱歉,如果这个问题太长。在我问之前,我需要说明它是从哪里来的 设立: 给定以下不可变类型矩形: class Rectangle { public Rectangle(double width, double height) { … } public double Width { get { … } } public double Height { get { … } } } public double Width { set { Contract.Ensur
矩形
:
class Rectangle
{
public Rectangle(double width, double height) { … }
public double Width { get { … } }
public double Height { get { … } }
}
public double Width
{
set { Contract.Ensures(Height == Contract.OldValue(Height)); }
}
public double Height
{
set { Contract.Ensures(Width == Contract.OldValue(Width)); }
}
…从中派生类型Square
似乎完全合法:
using System.Diagnostics.Contracts;
class Square : Rectangle
{
public Square(double sideLength) : base(sideLength, sideLength) { }
[ContractInvariantMethod]
void WidthAndHeightAreAlwaysEqual()
{
Contract.Invariant(Width == Height);
}
}
…因为派生类可以确保其自身的不变量不会被违反
但一旦我将矩形
设置为可变:
class Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
…
}
…我不应该再从它派生出Square
,因为Square
不应该有独立的宽度和高度设置器
问题:
如何处理代码契约,以便在我从可变的Rectangle
类派生Square
时,它会立即警告我契约违反?最好是,代码契约的静态分析已经在编译时给了我一个警告
换句话说,我的目标是用代码契约编码以下规则:
矩形的宽度
和高度
可以相互独立地改变
正方形的宽度
和高度
不能相互独立地改变,而这一点从一开始就没有意义
…并以这样一种方式进行:每当这些规则“冲突”时,代码契约都会注意到
到目前为止,我考虑的是:
1。向矩形添加不变量
:
class Rectangle
{
…
[ContractInvariantMethod]
void WidthAndHeightAreIndependentFromOneAnother()
{
Contract.Invariant(Width != Height || Width == Height);
}
}
public double Width
{
get { … }
set { Contract.Ensures(Height == Contract.OldValue(Height)); … }
}
public double Height
{
get { … }
set { Contract.Ensures(Width == Contract.OldValue(Width)); … }
}
这种方法的问题是,尽管不变量正确地表示“宽度和高度不一定相等,但它们可以相等”,但它是无效的,(1)因为它是重言式,(2)因为它比派生类Square
中的不变量宽度==高度限制性更小。也许在代码契约看到它之前,编译器甚至已经对它进行了优化
2。将post条件添加到矩形的setters:
class Rectangle
{
…
[ContractInvariantMethod]
void WidthAndHeightAreIndependentFromOneAnother()
{
Contract.Invariant(Width != Height || Width == Height);
}
}
public double Width
{
get { … }
set { Contract.Ensures(Height == Contract.OldValue(Height)); … }
}
public double Height
{
get { … }
set { Contract.Ensures(Width == Contract.OldValue(Width)); … }
}
这将禁止派生类Square
在Width
发生更改时简单地将Height
更新为Width
,反之亦然,它本身不会阻止我从矩形
派生Square
类。但这就是我的目标:让代码契约来警告我,Square
不能派生自可变的矩形
最近的MSDN杂志上有一篇非常相关的文章讨论了基本相同的问题:-我无法找到更好的答案
您认为的“非法”子类型基本上违反了Liskov替换原则,这篇文章展示了代码契约如何帮助检测此问题。中有一篇文章描述了您的问题,使用了相同的示例,并讨论了代码契约的使用和Liskov替换原理 感谢@BrokenGlass和@Mathias链接到MSDN杂志最近的一篇名为。虽然我相信这里面有问题(我马上就要谈到这个问题了……请看答案的第二部分),但它帮助我决定了解决方案
我决定的解决方案是:
我正在将以下post条件添加到基类Rectangle
:
class Rectangle
{
public Rectangle(double width, double height) { … }
public double Width { get { … } }
public double Height { get { … } }
}
public double Width
{
set { Contract.Ensures(Height == Contract.OldValue(Height)); }
}
public double Height
{
set { Contract.Ensures(Width == Contract.OldValue(Width)); }
}
它们基本上断言宽度
和高度
可以彼此独立设置
在派生类Square
中添加一个不变量:
Contract.Invariant(Width == Height);
这基本上是相反的
综上所述,这些最终将导致契约冲突——诚然不是在编译时,但似乎只有在某些特殊情况下(即,当派生类开始添加前置条件时)才能使用代码契约
为什么MSDN杂志文章中的解决方案没有帮助?
MSDN文章似乎展示了一个具有编译时警告优势的解决方案。为什么我要坚持使用上面的解决方案,而这个解决方案没有奖金
简短回答:
class Rectangle
{
…
[ContractInvariantMethod]
void WidthAndHeightAreIndependentFromOneAnother()
{
Contract.Invariant(Width != Height || Width == Height);
}
}
public double Width
{
get { … }
set { Contract.Ensures(Height == Contract.OldValue(Height)); … }
}
public double Height
{
get { … }
set { Contract.Ensures(Width == Contract.OldValue(Width)); … }
}
代码示例中的一个无法解释的变化清楚地表明,本文设置了一个人为的场景,以展示代码契约的静态分析
长答案:
class Rectangle
{
…
[ContractInvariantMethod]
void WidthAndHeightAreIndependentFromOneAnother()
{
Contract.Invariant(Width != Height || Width == Height);
}
}
public double Width
{
get { … }
set { Contract.Ensures(Height == Contract.OldValue(Height)); … }
}
public double Height
{
get { … }
set { Contract.Ensures(Width == Contract.OldValue(Width)); … }
}
本文从一个代码示例开始(→ 图1)一个矩形
类,其宽度
和高度
都有单独的设置器。下次显示矩形
类时(→ 图2),设置器是私有的,仅通过添加的方法SetSize(宽度、高度)
使用
这篇文章并没有解释为什么会悄悄地引入这一变化。事实上,仅在Rectangle
的上下文中,这种更改可能毫无意义,除非您已经知道将派生一个类,如Square
,其中需要添加一个前置条件width==height
。当宽度
和高度
的设置器彼此分离时,不能将此作为先决条件添加。如果您不能添加该前置条件,您将不会从代码契约中得到编译时警告。a-ha!“方法'Square.SetSize'覆盖了'Rectangle.SetSize',因此无法添加Requires.”太棒了!太糟糕了,它需要将宽度
和高度
的设置器人工重构为一个组合的设置大小
方法。。。我想知道是否有类似的解决方案覆盖了压缩变量方法
s?也许我应该再解释一下我的上述评论:他们对矩形
的整个重新设计使得两个setter组合成SetSize
似乎是为了炫耀代码契约:重构对矩形
(其中宽度的独立设置器