使用代码合同确保收集保持不变

时间:2014-04-16 08:04:15

标签: c# code-contracts design-by-contract

我试图实现方法的后置条件。我想保证它不会改变内部状态的某个特定部分(我修复了一个错误,因为它曾经这样做。对于回归目的,我在代码中添加了后置条件)。我最好的尝试如下:

Contract.Ensures(PropertyA.Collection.Count == Contract.OldValue(PropertyA.Collection).Count);
Contract.Ensures(Contract.ForAll(0, PropertyA.Collection.Count, index => this.PropertyA.Collection[index].Equals(Contract.OldValue(this.PropertyA.Collection)[index])));

此代码的问题是Contract.OldValue(PropertyA.Collection)在第二行引起了空引用异常。在第11.8节(http://research.microsoft.com/en-us/projects/contracts/userdoc.pdf)中的代码合同手册中指出,此特定Contract.ForAll应该与Contract.OldValue一起使用,但另一个超载不适用。

我是否有另一种方法可以检查PropertyA.Collection中的商品是否未发生变化,也未以某种方式重新排序?

2 个答案:

答案 0 :(得分:0)

您的问题尚不清楚,但我希望PropertyA.CollectionPropertyB.Collection是参考类型。 Contracts.OldValue()和引用类型的一个问题是,Contracts.OldValue()不保存指向的旧对象,而是保存指针本身。

因此,您的第一份合同将永远为真,因为您有效地将价值与自身进行比较。 写它的正确方法是

Contract.Ensures(PropertyA.Collection.Count == Contract.OldValue(PropertyA.Collection.Count));

我不确定为什么你的第二份合同失败了。 通常,在尝试比较方法调用之前和之后的集合时,需要将集合保存为可枚举:

Contract.OldValue(PropertyA.Collection.ToList())
// or
Contract.OldValue(PropertyA.Collection.ToArray())

这将评估表达式并将尊重保存到列表/数组中,然后可以将其编入索引。

答案 1 :(得分:0)

很抱歉这么晚回答,但代码合同中没有太多活动。

我认为问题有两个方面:

1)为了确保条件,正如您所问,我认为可以使用[Pure]函数来完成,并在Contract.Ensures中调用此函数

2)不幸的是它只能在本地解决问题。请注意这种情况:

1-调用一个确保收集条件的函数  2-调用一个确保集合无变化的功能  3-调用具有(1)

中确定的条件前提条件的函数

(2)中的[Pure]函数确保集合中不进行任何更改,不允许CodeContracts推断(1)中的后置条件确保(3)中的前提条件。

我认为CodeContracts应该提供一种功能,以确保对象不会发生变化,但将对象作为“深层对象”;能让你写出像

这样的东西

提案代码 Contract.Ensures(PureArgument(PropertyA.Collection))

由于功能 PureArgument 应该是CodeContracts的一部分,它可能是一个属性:

提案代码 [PureArgument(PropertyA.Collection)]

请注意,CodeContracts可以检查 PureArgument 属性是否已满足,因为它可以进行深度比较;检查真的很复杂和昂贵(它需要深层复制),但它可以完成。但是检查不是问题,而是检查代码以确保参数没有深度改变,这并不总是可行。

无论如何,我确信这样的功能会很棒,即使没有检查它是否填满了断言,并且没有检查代码以确保参数没有改变(这比检查断言是完整的,更有趣,但根本无法掩盖。

回到这个问题,确保集合不会改变的解决方案可能是使用 new ReadOnlyCollection(PropertyA.Collection)作为参数,而不是使用PropertyA,但它是一个改变代码。