Code Contracts, null checks and value/reference types

Updated post : In order to avoid confusion about what I am and am not doing, I have edited this post radically to include a complete example of the code that causes this problem. In order to make this post readable anyway, all the code is posted at the bottom.


Background:

I am writing a fluent interface for testing (I know it's been done, but half of the purpose is to learn how it works...), in which I want to verify that myNumber is between 3 and 10 with a code line like

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

I think you can see by reading the second line, what it is supposed to verify. There are more complicated test cases, of course...

To enable the .And syntax, I have introduced a helper type called AndHelper , which is returned by each verification method and which has a property And that returns whatever was tested. So .And in the previous example, should return myNumber so I can test another condition as well.

I'm using Code Contracts, and among other things I'm verifying that the this argument of some of these extensions is non-null. This is that causes my problem.


My problem:

When running the Code Contract checks on my code, I get a bunch of warnings that the non-null requirement on for example ShouldBeA<T> cannot be verified. I've tried to solve this by subclassing AndHelper<T> with two classes, ReferenceAndHelper<T> and StructAndHelper<T> , and ReferenceAndHelper<T> has contracts that should guarantee that the non-null requirement is met. However, this does not seem to work.

For each time I use one of these test extensions, I get two warning messages. One stating that the contract "instance != null" could not be verified, and the other stating the location. The first points to the line where I use the method (for example line 2 in my first example) and the second one to the line where the contract is specified, marked with // (1) in my code.


My code:

Please bear with me that this part of the post is quite lengthy. I don't know what SO guidelines are on posting large chunks of code (that is still relevant), but if there is a better way, please enlighten me.

Note that there is code in this section that does not cause this specific error, but that introduces limits on the solution. For example, I must have a type ( AndHelper<T> or a subclass) that is class/struct ignorant.

A couple of the tests:

// 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);
}

The AndHelper<T> class:

public class AndHelper<T>
{
    protected readonly T val;

    public AndHelper(T value)
    {
        this.val = value;
    }

    public virtual T And { get { return this.val; } }
}

The two subclasses, 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);
    }
}

and 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;
        }
    }
}

Instead of creating two AndHelper<T> classes with different constraints, could you simply create a NonNullAndHelper<T> that asserts the invariant that its value is not null? This would only be returned by helper functions that can guarantee their result is non-null, either due to a requirement or as a side-effect of their function (like IsNotNull). This should allow the contracts to prove.


there is no way for Code Contracts to verify that And (the property on AndHelper) will never return null

Why not? Unless I'm misunderstanding your question, you could code something like this:

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);
    }
}

From there out, the contract checker will ensure the And value will never be null.

Am I misunderstanding your question?


I know this is an old question, but I see no accepted answers, so thought I'd take a shot.

Rather than having two subclasses of AndHelper<T> , change your AndHelper<T> to be the following:

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) does not raise the warning for generic types but guarantees they are not null.

链接地址: http://www.djcxy.com/p/59600.html

上一篇: 代码合同并确保不引发异常

下一篇: 代码合同,空检查和值/参考类型