3

この質問が長すぎると思われる場合は申し訳ありません。質問する前に、それがどこから来ているのかを示す必要があります。

設定:

次の不変タイプが与えられます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ので、私はもはやそれから派生するべきではありません。SquareWidthHeight

質問:

Square可変Rectangleクラスから派生するとすぐにコントラクト違反を警告するように、コードコントラクトで何ができますか?できれば、コードコントラクトの静的分析により、コンパイル時にすでに警告が表示されます。

言い換えれば、私の目標は、コードコントラクトを使用して次のルールをエンコードすることです。

  • Widthおよびのは、互いに独立して変更Heightすることができます。Rectangle
  • WidthHeightのはSquare互いに独立して変更することはできず、そもそもそれは意味がありません。

…そして、これらのルールが「衝突」するたびにコードコントラクトが気付くような方法でそれを行います。

私がこれまで考えてきたこと:

1.不変条件をRectangle:に追加します

class Rectangle
{
    …
    [ContractInvariantMethod]
    void WidthAndHeightAreIndependentFromOneAnother()
    {
        Contract.Invariant(Width != Height || Width == Height);
    }
}

このアプローチの問題は、不変条件が「幅と高さは等しい必要はありませんが、等しくなる可能性があります」と正しく述べている一方で、(1)トートロジーであるため、(2)それがWidth == Height派生クラスの不変条件よりも制限が少なくなりSquareます。おそらく、コードコントラクトがそれを見る前に、コンパイラによって最適化されているのかもしれません。

Rectangle2.のセッターに事後条件を追加します。

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

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

これにより、派生クラスが変更されるたびにSquare更新Heightされることは禁止され、その逆も同様です。それ自体がからクラスを派生することを妨げることはありません。しかし、それが私の目標です。コードコントラクトに、可変から派生してはならないことを警告してもらうことです。WidthWidthSquareRectangleSquareRectangle

4

3 に答える 3

4

最近のMSDNマガジンには、基本的に同じ問題について説明している非常に関連性の高い記事がありました。「コードコントラクト:継承とリスコフの原則」 -これ以上の答えは思いつきません。

「違法な」サブタイピングとみなすのは、基本的にリスコフの置換原則の違反です。この記事では、コードコントラクトがこの問題の検出にどのように役立つかを示しています。

于 2011-08-07T23:15:09.490 に答える
2

MSDN Magazineの2011年7月号に、同じ例で問題のほとんどを説明し、コードコントラクトの使用とリスコフの置換原則について説明している記事があります。

于 2011-08-07T23:15:23.583 に答える
0

最近のMSDNMagazineの記事「CodeContracts :Inheritance and theLiskovPrinciple」へのリンクを提供してくれた@BrokenGlassと@Mathiasに感謝します。何か問題があると思いますが(すぐにわかります…この回答の2番目の部分を参照してください)、解決策を決定するのに役立ちました。

私が決めた解決策:

  1. 基本クラスに次の事後条件を追加していますRectangle

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

    これらは基本的にそれを主張しWidthHeight互いに独立して設定することができます。

  2. 派生クラスに1つの不変条件が追加されSquareます:

    Contract.Invariant(Width == Height);
    

    これは基本的に正反対のことを述べています。

まとめると、これらは最終的にコントラクト違反になります—確かにコンパイル時ではありませんが、特定のコーナーケース(つまり、派生クラスが前提条件の追加を開始するとき)でのみコードコントラクトで実行できるようです。

MSDN Magazineの記事の解決策が役に立たないのはなぜですか?

MSDNの記事には、コンパイル時の警告という利点があるソリューションが示されているようです。このボーナスがない上記の解決策に固執するのはなぜですか?

短い答え:

コード例の説明のつかない変更により、この記事がコードコントラクトの静的分析を披露するために人為的な状況を設定していることが明らかになりました。

長い答え:

この記事は、との両方にRectangle個別のセッターを持つクラスのコード例(→図1)から始まります。次回クラスが表示されるとき(→図2)、セッターが作成され、追加されたメソッドを介してのみ使用されます。WidthHeightRectangleprivateSetSize(width, height)

この記事では、この変更が黙って導入された理由については説明されていません。実際、前提条件を追加する必要がある場所Rectangleのようなクラスを派生させることをもちろん知っている場合を除いて、その変更は、単独のコンテキストではおそらくまったく意味がありません。セッターとが互いに分離されている場合、これを前提条件として追加することはできません。また、その前提条件を追加できなかった場合、コードコントラクトからコンパイル時の警告は表示されません。Squarewidth == heightWidthHeight

于 2011-08-08T09:20:35.743 に答える