c# – 代码合同,空检查和值/引用类型

更新的帖子:为了避免混淆我现在和未做的事情,我已经彻底编辑了这篇文章,以包含导致此问题的代码的完整示例.为了使这篇文章可读,所有代码都发布在底部.

背景:

我正在编写一个流畅的测试界面(我知道它已经完成了,但目的的一半是学习它是如何工作的……),其中我要验证myNumber在3到10之间,代码行是这样的

myNumber.ShouldBeLessThan(10).And.ShouldBeGreaterThan(10);
myListOfCars.ShouldNotBeNull().And.ShouldBeA<IEnumerable<Car>>();

我想你可以通过阅读第二行看到它应该验证的内容.当然还有更复杂的测试用例……

为了启用.And语法,我引入了一个名为AndHelper的帮助器类型,它由每个验证方法返回,并且具有一个属性并返回被测试的内容.所以.在上一个例子中,应该返回myNumber,这样我也可以测试另一个条件.

我正在使用代码契约,除此之外,我正在验证其中某些扩展的这个参数是非空的.这是导致我的问题.

我的问题:

当我的代码运行代码合同检查时,我收到一堆警告,表示非空要求,例如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>断言其值不为空的不变量?这只能由辅助函数返回,这些辅助函数可以保证它们的结果非空,这可能是由于需求或其函数的副作用(如IsNotNull).这应该允许合同证明.

点赞