我有这个结构(简化为简洁):
public struct Period
{
public Period(DateTime? start, DateTime? end) : this()
{
if (end.HasValue && start.HasValue && end.Value < start.Value)
{
throw new ArgumentOutOfRangeException("end", "...");
}
Contract.EndContractBlock();
this.start = start;
this.end = end;
}
private readonly DateTime? start;
private readonly DateTime? end;
public static Period operator +(Period p, TimeSpan t)
{
Contract.Assume(!p.start.HasValue || !p.end.HasValue || p.start.Value <= p.end.Value);
return new Period(
p.start.HasValue ? p.start.Value + t : (DateTime?) null,
p.end.HasValue ? p.end.Value + t : (DateTime?) null);
}
}
但静态检查员正在给我这个警告:
CodeContracts:需要未经证实:end.HasValue&amp;&amp; start.HasValue&amp;&amp; end.Value&gt; = start.Value
从自定义参数验证中推断出的这个要求是完全错误的。我想允许start
或end
的空值,如果两者都提供,则只需要start <= end
。但是,如果我将构造函数更改为:
public Period(DateTime? start, DateTime? end) : this()
{
Contract.Requires(!start.HasValue || !end.HasValue || start.Value <= end.Value);
this.start = start;
this.end = end;
}
我收到此警告,看起来更正确,但很难理解为何无法证明这些要求:
CodeContracts:需要未经证实:!start.HasValue || !end.HasValue || start.Value&lt; = end.Value
我认为它可能会遇到?:
的问题,但是当我将操作符更改为:
public static Period operator +(Period p, TimeSpan t)
{
var start = p.start.HasValue ? p.start.Value + t : (DateTime?) null;
var end = p.end.HasValue ? p.end.Value + t : (DateTime?) null;
Contract.Assume(!start.HasValue || !end.HasValue || start.Value <= end.Value);
return new Period(start, end);
}
当然,如果我将.Requires
更改为.Assume
,则警告会完全消失,但这不是一个可接受的解决方案。
因此,Code Contracts中的静态检查器似乎无法正确反转条件。它不是简单地通过用!(…)
包裹它或应用De Morgan's law来反转条件(如上所示),而是反转条件的最后一个组成部分。使用自定义参数验证时,静态检查程序无法正确解释复杂条件吗?
有趣的是,我试过这个,认为静态检查器会将!
从前面剥离,但不会:
if (!(!start.HasValue || !end.HasValue || start.Value <= end.Value))
{
throw new ArgumentOutOfRangeException("end", "...");
}
Contract.EndContractBlock();
CodeContracts:需要未经证实:!(!(!start.HasValue ||!end.HasValue || start.Value&lt; = end.Value))
在这种情况下,它 只用!(…)
包裹整个条件,即使它没有。
另外,如果我将可空的DateTime
更改为普通的非可空DateTime
并重写这样的合同,它会按预期工作而不会发出任何警告:
public struct Period
{
public Period(DateTime start, DateTime end) : this()
{
Contract.Requires(start <= end);
this.start = start;
this.end = end;
}
private readonly DateTime start;
private readonly DateTime end;
public static Period operator +(Period p, TimeSpan t)
{
Contract.Assume(p.start + t <= p.end + t); // or use temp variables
return new Period(p.start + t <= p.end + t);
}
}
但仅使用Contract.Assume(p.start <= p.end)
将无效。
CodeContracts:需要unproven:start&lt; = end
答案 0 :(得分:3)
我认为部分问题可能是您在Contract.Requires
电话中使用的条件。
以你的一个构造函数为例:
public Period(DateTime? start, DateTime? end) : this()
{
Contract.Requires(!start.HasValue || !end.HasValue || start.Value <= end.Value);
this.start = start;
this.end = end;
}
如果start.HasValue
为false
(意味着!start.HasValue
为true
),但end
确实有值,该怎么办? start.value <= end.Value
在这种情况下评估的是什么,因为一个是null
而另一个是值?
相反,您的Contract.Requires
条件应如下所示:
Contract.Requires(!(start.HasValue && end.HasValue) || start.Value <= end.Value);
如果start
或end
中的任何一个没有值,则条件将返回true
(并且OR条件短路,从不评估是否start.Value <= end.Value
)。但是,如果start
和end
都分配了值,则条件的第一部分返回false
,此时start.Value
必须小于或等于end.Value
以使条件总体评估为true
。这就是你追求的目标。
以下是您的问题:Period
的任何实例都要求start.Value <= end.Value
或其中一个或另一个(或两者)null
,这是真的吗?如果是这样,您可以将其指定为不变。这意味着在任何方法进入或退出时,!(start.HasValue && end.HasValue) || start.Value <= end.Value
必须成立。这可以在合理的时候简化你的合同。
<强>更新强>
查看我在评论(TDD and Code Contracts)中发布的博客文章,您可以使用代码合约operator +(Period p, TimeSpan t)
属性安全地注释您的PureAttribute
实施。此属性告诉Code Contracts静态分析器该方法不会改变调用该方法的对象的任何内部状态,因此不会产生副作用:
[Pure]
public static Period operator +(Period p, TimeSpan t)
{
Contract.Requires(!(p.start.HasValue && p.end.HasValue) || p.start.Value <= p.end.Value)
return new Period(
p.start.HasValue ? p.start.Value + t : (DateTime?) null,
p.end.HasValue ? p.end.Value + t : (DateTime?) null);
}
<强>更新强>
好的,我更多地想到了这一点,我想我现在明白了Code Contracts与你的合同有关的问题。我认为您还需要在构造函数中添加Contract.Ensures
合约(即帖子条件合约):
public Period(DateTime? start, DateTime? end) : this()
{
Contract.Requires(!(start.HasValue && end.HasValue) || start.Value <= end.Value);
Contract.Ensures(!(this.start.HasValue && this.end.HasValue) || this.start.Value <= this.end.Value);
this.start = start;
this.end = end;
}
这告诉代码合同,当构造函数退出时,对象的start
和end
字段(如果它们都有值)必须满足start.Value <= end.Value
的条件。如果不满足该条件,(可能)代码合同将抛出异常。这也应该有助于静态分析仪。
更新(再次,主要是为了完整性)
我在&#34;未经证实的&#34;上做了更多的调查。警告。 Requires
和Ensures
都可能发生这种情况。这是另一个遇到类似问题的人的例子:http://www.go4answers.com/Example/ensures-unproven-contractresult-79084.aspx。
添加合约不变量可以按如下方式进行(对于OP所讨论的代码):
[ContractInvariantMethod]
protected void PeriodInvariants()
{
Contract.Invariant(!(start.HasValue && end.HasValue) || start.Value <= end.Value);
}
每次进入/退出对象的方法时都会调用此方法,以确保此条件成立。
另一篇应该证明有趣的博客文章
我发现了另一个可能有趣的博客条目:http://www.rareese.com/blog/net-code-contracts
在这种情况下,我不同意作者的解决方案&#34;摆脱requires unproven
警告。这是作者的代码:
public static void TestCodeContract(int value)
{
if(value > 100 && value < 110)
TestLimits(value);
}
public static void TestLimits(int i)
{
Contract.Requires(i > 100);
Contract.Requires(i < 110);
//Do Something
}
在这里,问题的真正解决方案应该如下:
public static void TestCodeContract(int value)
{
Contract.Requires(value > 100 && value < 110);
// You could alternatively write two Contract.Requires statements as the blog
// author originally did.
}
这也应该消除警告,因为静态分析器现在知道value
必须在101到109的范围内,这也恰好满足TestLimits
方法的合同标准。
因此,我建议您检查调用Period
构造函数的位置和/或Period.operator +(...)
方法,以确保调用方法也具有必要的Contract.Requires
语句(或者,Contract.Assume
,它告诉静态分析器假设提供的条件为真。)
使用代码合约时,您需要检测所有代码。你通常不能选择&#34;哪个部分用于指定合同,因为静态分析器很可能没有足够的信息来完成其分析(因此,确保合同得到保证),您将收到许多警告。