代码合同,空检查和值/参考类型
更新后的帖子 :为了避免混淆我的和未做的事情,我已经从根本上编辑了这篇文章,以包含导致此问题的完整代码示例。 为了使这个帖子可读,所有的代码都发布在底部。
背景:
我正在编写一个流畅的界面进行测试(我知道它已经完成了,但其中一半的目的是了解它是如何工作的......),我想通过代码行来验证myNumber
是否在3到10之间
myNumber.ShouldBeLessThan(10).And.ShouldBeGreaterThan(10);
myListOfCars.ShouldNotBeNull().And.ShouldBeA<IEnumerable<Car>>();
我想你可以通过阅读第二行来看到它应该验证的内容。 当然有更复杂的测试案例...
要启用.And
语法,我已经介绍了被称为辅助型AndHelper
,这是由每个验证方法和具有属性返回And
,无论是测试的回报。 所以.And
在前面的例子中,应该返回myNumber
以便我可以测试另一个条件。
我正在使用Code Contracts,除此之外,我正在验证其中一些扩展的this
参数是非空的。 这是我的问题。
我的问题:
在代码上运行代码合同检查时,我收到了一堆警告,例如ShouldBeA<T>
上的非空要求无法验证。 我尝试通过AndHelper<T>
两个类, ReferenceAndHelper<T>
和StructAndHelper<T>
,并且ReferenceAndHelper<T>
具有应保证满足非空要求的合约。 但是,这似乎并不奏效。
每次使用这些测试扩展时,我都会收到两条警告消息。 一个说明合同“instance!= null”无法验证,另一个说明位置。 第一个指向我使用该方法的行(例如第一个示例中的第2行),第二个指向指定合同的行,在代码中用// (1)
标记。
我的代码:
请耐心等待这篇文章的篇幅很长。 我不知道SO指南是如何发布大量代码(这仍然是相关的),但如果有更好的方法,请赐教。
请注意,本节中的代码不会导致此特定错误,但会对解决方案造成限制。 例如,我必须有一个类型( AndHelper<T>
或一个子类),它是类/结构无知的。
一些测试:
// This test requires that instance != null, and therefore works
// with ReferenceAndHelper<T>
public static ReferenceAndHelper<T> ShouldBeA<T>(this object instance, string message = "")
where T : class
{
Contract.Requires<ArgumentNullException>(instance != null); // (1)
Contract.Ensures(Contract.Result<ReferenceAndHelper<T>>() != null);
Assert.IsInstanceOf<T>(instance, message.AsNullIfWhitespace() ?? string.Format("ShouldBeA<{0}> failed.", typeof(T).Name));
return new ReferenceAndHelper<T>((T)instance);
}
// This test should work for both class and struct types T, and therefore
// cannot decide between StructAndHelper<T> and ReferenceAndHelper<T>.
// The base class is used.
public static AndHelper<T> ShouldBeGreaterThan<T>(this T actual, T expected, string message = "")
where T : IComparable
{
Contract.Ensures(Contract.Result<AndHelper<T>>() != null);
(actual.CompareTo(expected) > 0).ShouldBeTrue(message.AsNullIfEmpty() ?? string.Format("ShouldBeGreaterThan failed. {0} is not greater than {1}", actual.ToString(), expected.ToString()));
return new AndHelper<T>(actual);
}
// This is the test that returns the AndHelper<T> that .And is called on.
// It is, as you can see, in all possible ways specified that this will be a
// ReferenceAndHelper<T>, which has contracts to ensure that the value is not null.
public static ReferenceAndHelper<T> ShouldNotBeNull<T>(this T value, string message = "")
where T : class
{
Contract.Requires<ArgumentNullException>(value != null);
Contract.Ensures(Contract.Result<ReferenceAndHelper<T>>() != null);
Assert.IsNotNull(value, message.AsNullIfWhitespace() ?? "ShouldNotBeNull failed.");
return new ReferenceAndHelper<T>(value);
}
AndHelper<T>
类:
public class AndHelper<T>
{
protected readonly T val;
public AndHelper(T value)
{
this.val = value;
}
public virtual T And { get { return this.val; } }
}
这两个子类, ReferenceAndHelper<T>
:
public class ReferenceAndHelper<T> : AndHelper<T>
where T : class
{
public ReferenceAndHelper(T value)
: base(value)
{
Contract.Requires(value != null);
}
public override T And
{
get
{
Contract.Ensures(Contract.Result<T>() != null);
return val;
}
}
[ContractInvariantMethod]
void ValueIsNotNullInvariant()
{
Contract.Invariant(this.val != null);
}
}
和StructAndHelper<T>
:
public class StructAndHelper<T> : AndHelper<T>
where T : struct
{
public StructAndHelper(T value)
: base(value)
{
}
public override T And
{
get
{
return this.val;
}
}
}
不用创建两个具有不同约束的AndHelper<T>
类,你可以简单地创建一个NonNullAndHelper<T>
来声明它的值不为null的不变量吗? 这只会由辅助函数返回,这可以保证它们的结果非空,无论是由于需求还是作为其函数的副作用(如IsNotNull)。 这应该允许合同证明。
代码合同无法验证And(AndHelper上的属性)永远不会返回null
为什么不? 除非我误解了你的问题,否则你可以编写这样的代码:
public class AndHelper<T>
{
protected readonly T val;
public T And { get { return val; } }
public AndHelper(T value)
{
Contract.Requires(value != null);
val = value;
}
[ContractInvariantMethod]
void Invariants()
{
Contract.Invariant(And != null);
}
}
从那里,合同检查员将确保And值永远不会为空。
我误解你的问题吗?
我知道这是一个古老的问题,但我看不到任何接受的答案,所以我认为我会采取一些措施。
而不是有两个AndHelper<T>
子类,将您的AndHelper<T>
更改为以下内容:
public class AndHelper<T>
{
private readonly T val;
public AndHelper(T value)
{
Contract.Requires(!ReferenceEquals(value, null));
this.val = value;
}
public virtual T And
{
get
{
Contract.Ensures(!ReferenceEquals(Contract.Result<T>(), null));
return this.val;
}
}
[ContractInvariantMethod]
private void ObjectInvariant()
{
Contract.Invariant(!ReferenceEquals(val, null));
}
}
ReferenceEquals(object, object)
不会引发泛型类型的警告,但保证它们不为null。