我可以获得Code Contracts来警告我“非法”的子类型吗?

时间:2011-08-07 23:09:15

标签: c# compiler-warnings code-contracts invariants subtyping

很抱歉,这个问题似乎太长了。在我问之前,我需要展示它的来源。

设置:

给出以下不可变类型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不应该为WidthHeight设置独立的设置器。

问题:

我可以对代码合同做些什么,一旦我从可变Square类派生Rectangle,它就会警告我合同违规?最好是,Code Contracts的静态分析在编译时会给我一个警告。

换句话说,我的目标是使用代码合同对以下规则进行编码:

  • WidthHeight的{​​{1}}可能会彼此独立更改。
  • {li> RectangleWidth的{​​{1}}不能彼此独立更改,而且首先没有意义。

...并且只要这些规则“碰撞”,代码合约就会注意到这一点。

到目前为止我已经考虑过了:

1。向Height添加不变量:

Square

这种方法的问题在于,虽然不变量正确地指出,“宽度和高度不必相等,但它们可以是”,它是无效的,(1)因为它是重言式,并且(2) )因为它比派生类Rectangle中的不变class Rectangle { … [ContractInvariantMethod] void WidthAndHeightAreIndependentFromOneAnother() { Contract.Invariant(Width != Height || Width == Height); } } 限制性更小。也许它甚至在Code Contracts看到它之前就被编译器优化了。

2。将后置条件添加到Width == Height的二传手:

Square

这将禁止派生类Rectanglepublic 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

3 个答案:

答案 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。虽然我认为它有些不对劲(我会在一秒钟内完成...看到这个答案的第二部分),这有助于我决定解决方案。

解决方案我决定:

  1. 我将以下后置条件添加到基类Rectangle

    public double Width
    {
        set { Contract.Ensures(Height == Contract.OldValue(Height)); }
    }
    
    public double Height
    {
        set { Contract.Ensures(Width == Contract.OldValue(Width)); }
    }
    

    这些基本断言WidthHeight可以彼此独立设置。

  2. 在派生类Square中添加了一个不变量:

    Contract.Invariant(Width == Height);
    

    这基本上恰恰相反。

  3. 总而言之,这些最终会导致合同违规 - 诚然不是在编译时,但似乎只能在某些极端情况下使用代码合同(即派生时)课程开始添加前提条件。)

    为什么MSDN杂志文章中的解决方案没有帮助?

    MSDN文章似乎显示了一个具有编译时警告优势的解决方案。为什么我坚持上面没有这个奖金的解决方案?

    简答:

    代码示例中无法解释的变化清楚地表明文章设置了一个人为的情况,以展示Code Contracts的静态分析。

    答案很长:

    本文首先介绍Rectangle类的代码示例(→图1),其中包含WidthHeight的各个setter。下次显示Rectangle类时(→图2),设置者已设为private,仅通过添加的方法SetSize(width, height)使用。

    文章没有解释为什么这种改变是默默引入的。事实上,在Rectangle单独的情况下,这种变化可能根本没有任何意义,除非你已经知道你会得到像Square这样的类,你需要添加一个前条件width == height。当WidthHeight的设置者彼此分开时,您无法将此作为前置条件添加。如果您无法添加该前提条件,则不会从代码合同中获得编译时警告。