Кодовые контракты, нулевые проверки и типы значений / ссылок - PullRequest
4 голосов
/ 08 марта 2011

Обновленный пост : Во избежание путаницы в том, что я делаю и чем не занимаюсь, я радикально отредактировал этот пост, чтобы включить в него полный пример кода, вызывающего эту проблему.В любом случае, чтобы сделать это сообщение читабельным, весь код размещен внизу.


Справочная информация:

Я пишу свободный интерфейс для тестирования (я знаю, что этосделано, но половина цели состоит в том, чтобы узнать, как это работает ...), в котором я хочу убедиться, что 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;
        }
    }
}

Ответы [ 3 ]

1 голос
/ 08 марта 2011

у Code Contracts нет возможности проверить, что 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 никогда не будет нулевым.

Я неправильно понял ваш вопрос?

1 голос
/ 08 марта 2011

Вместо создания двух AndHelper<T> классов с различными ограничениями, вы могли бы просто создать NonNullAndHelper<T>, который утверждает инвариант, что его значение не равно нулю?Это может быть возвращено только вспомогательными функциями, которые могут гарантировать, что их результат не равен нулю, либо из-за требования, либо как побочный эффект их функции (например, IsNotNull).Это должно позволить контрактам доказать.

0 голосов
/ 18 февраля 2013

Я знаю, что это старый вопрос, но я не вижу принятых ответов, поэтому подумал, что мне нужно сделать попытку.

Вместо того, чтобы иметь два подкласса 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) не выдает предупреждение для универсальных типов, но гарантирует, что они не равны нулю.

...