CodeContracts с типами коллекций - PullRequest
6 голосов
/ 25 ноября 2011

У меня есть коллекция дочерних элементов в моем классе, и у меня есть открытый доступ к ней. Я хочу предоставить постусловие, чтобы гарантировать, что элементы в коллекции не равны нулю (я знаю, что в тестах 2 и 3 вызывающая сторона может изменить коллекцию, но сейчас моя цель - просто убедиться, что коллекция, возвращенная из свойства, не не содержит ноль элементов).

Я думал, что использования Assume и ForAll будет достаточно, но это не поможет

Вот пример кода с 3 классами, которые я пробовал. Все 3 случая практически идентичны, за исключением того, что сначала выставляются ReadOnlyObservableCollection , 2-й - ObservableCollection и 3-й - Список .

- ReadOnlyObservableCollection

class Test1
{
  public Test1()
  {
    _children = new ObservableCollection<A>();
    _childrenReadOnly = new ReadOnlyObservableCollection<A>(_children);
  }

  protected readonly ObservableCollection<A> _children;
  protected readonly ReadOnlyObservableCollection<A> _childrenReadOnly;

  public ReadOnlyObservableCollection<A> Children
  {
    get
    {
      Contract.Ensures(Contract.ForAll(Contract.Result<ReadOnlyObservableCollection<A>>(), i => i != null));
      Contract.Assume(Contract.ForAll(_childrenReadOnly, i => i != null));
      return _childrenReadOnly; // CodeContracts: ensures unproven: Contract.ForAll(Contract.Result<ReadOnlyObservableCollection<A>>(), i => i != null)
    }
  }

  [ContractInvariantMethod]
  private void ObjectInvariant()
  {
    Contract.Invariant(_children != null);
    Contract.Invariant(_childrenReadOnly != null);
  }
}

- ObservableCollection

class Test2
{
  public Test2()
  {
    _children = new ObservableCollection<A>();
  }

  protected readonly ObservableCollection<A> _children;

  public ObservableCollection<A> Children
  {
    get
    {
      Contract.Ensures(Contract.ForAll(Contract.Result<ObservableCollection<A>>(), i => i != null));
      Contract.Assume(Contract.ForAll(_children, i => i != null));
      return _children; // CodeContracts: ensures unproven: Contract.ForAll(Contract.Result<ObservableCollection<A>>(), i => i != null)
    }
  }

  [ContractInvariantMethod]
  private void ObjectInvariant()
  {
    Contract.Invariant(_children != null);
  }
}

- Список

class Test3
{
  protected readonly List<A> _children = new List<A>();

  public List<A> Children
  {
    get
    {
      Contract.Ensures(Contract.ForAll(Contract.Result<List<A>>(), i => i != null));
      Contract.Assume(Contract.ForAll(_children, i => i != null));
      return _children; // No warning here when using List instead of ObservableCollection
    }
  }

  [ContractInvariantMethod]
  private void ObjectInvariant()
  {
    Contract.Invariant(_children != null);
  }
}

Вот тестовый код, который использует эти классы:

  Test1 t1 = new Test1();
  foreach (A child in t1.Children)
  {
    child.SomeMethod(); // CodeContracts: Possibly calling a method on a null reference 'child'
  }

  Test2 t2 = new Test2();
  foreach (A child in t2.Children)
  {
    child.SomeMethod(); // CodeContracts: Possibly calling a method on a null reference 'child'
  }

  Test3 t3 = new Test3();
  foreach (A child in t3.Children)
  {
    child.SomeMethod(); // CodeContracts: Possibly calling a method on a null reference 'child'
  }

Можно ли как-то определить договор, чтобы не писать Contract.Assume(child != null) каждый раз, когда я использую Children свойство?


Обновление:

Я попытался реализовать Enumerator, который обеспечивает ненулевое условие в Current getter свойства, как было предложено phoog . Но предупреждение все еще присутствует (удивительно для меня).

public class NotNullEnumerable<T> : IEnumerable<T>
{
    private IEnumerable<T> _enumerable;
    public NotNullEnumerable(IEnumerable<T> enumerable)
    {
        _enumerable = enumerable;
    }

    #region IEnumerable<T> Members
    public IEnumerator<T> GetEnumerator()
    {
        return new NotNullEnumerator<T>(_enumerable.GetEnumerator());
    }
    #endregion

    #region IEnumerable Members
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
    #endregion
}

public class NotNullEnumerator<T> : IEnumerator<T>
{
    private readonly IEnumerator<T> _enumerator;
    public NotNullEnumerator(IEnumerator<T> enumerator)
    {
        _enumerator = enumerator;
    }

    #region IEnumerator<T> Members
    public T Current
    {
        get
        {
            Contract.Ensures(Contract.Result<T>() != null);
            return _enumerator.Current;
        }
    }
    #endregion

    #region IDisposable Members
    public void Dispose()
    {
        _enumerator.Dispose();
    }
    #endregion

    #region IEnumerator Members
    object System.Collections.IEnumerator.Current
    {
        get
        {
            Contract.Ensures(Contract.Result<object>() != null);
            return _enumerator.Current;
        }
    }

    public bool MoveNext()
    {
       return _enumerator.MoveNext();
    }

    public void Reset()
    {
        _enumerator.Reset();
    }
    #endregion
}

Использование в коде:

        Test1 t1 = new Test1();
        var NonNullTest1 = new NotNullEnumerable<A>(t1.Children);
        foreach (A child in NonNullTest1)
        {
            child.SomeMethod(); // CodeContracts: Possibly calling a method on a null reference 'child'
        }

Есть идеи?

Ответы [ 3 ]

1 голос
/ 25 сентября 2015

Я пытался достичь того же подхода, и чаще всего я сталкивался с:

  1. Объявите универсальный интерфейс INonNullable только с одним свойством, которое должно возвращать ненулевое значение, реализуйте его в структуре NonNullable.
  2. Объявите интерфейсы INonNullEnumerator и INonNullEnumerable (вероятнее всего, это было бесполезно), так же, как IEnumerator и IEnumerable, но INonNullEnumerable имеет свойство IsEmpty, а GetEnumerator требует, чтобы оно было ложным. NonNullEnumerator возвращает T, а не INonNullable .
  3. Объявите мою собственную коллекцию, реализующую INonNullEnumerable и IList > (для совместимости со здравым смыслом IEnumerable и здравым смыслом), основанные на массиве NonNullable. Методы IList реализованы явно с аргументами INonNullable, но с такими же неявными методами, принимающими значения T с контрактами.

В результате эта гидра может быть передана как обычный аргумент IEnumerable, возвращая значения INonNullable (которые все еще требуются статической проверкой для проверки на нулевые значения, поскольку это ссылочный тип), в то время как значения T с ненулевой гарантией может использоваться в методах и в выражении foreach (потому что foreach использует неявный метод GetEnumerator (), который возвращает INonNullEnumerator, который гарантирует возврат ненулевого INonNullable, который является структурой NonNullable, и все это поддерживается контрактами).

Но, если честно, это монстр. Я закодировал его просто, чтобы сделать все возможное, чтобы заключить контракты, чтобы гарантировать, что в коллекции нет нулевых значений. Тем не менее, без полного успеха: Contract.ForAll (myList, item => item! = Null) не может быть доказан только потому, что он использует IEnumerable, ни foreach, ни мой INonNullEnumerable.

Моя ставка, это невозможно, по крайней мере с текущим API CodeContracts.

1 голос
/ 25 ноября 2011

Я бы создал свой собственный тип коллекции. Например, вы могли бы реализовать IList<T> и «гарантировать», что средство получения индекса никогда не возвращает ноль, и «потребовать», чтобы Add() и установщик индекса никогда не получал значение NULL в качестве аргумента.

EDIT:

Чтобы избежать сообщения «возможно, вызов метода по нулевой ссылке» в цикле foreach, вам, вероятно, также потребуется реализовать собственный тип перечислителя и «убедиться», что его свойство Current никогда не возвращает ноль.

EDIT2:

Поскольку ObservableCollection<> и ReadOnlyObservableCollection<> оба украшают экземпляр IList<> и, следовательно, реализуют IList<>, я попробовал следующее. Обратите внимание на несоответствие между «гарантирует недоказанность» и «утверждение ложно». Я получил те же сообщения, независимо от того, был ли статический тип result ReadOnlyObservableCollection<C> или IList<C>. Я использую Code Contracts версии 1.4.40602.0.

namespace EnumerableContract
{
    public class C
    {
        public int Length { get; private set; }
    }

    public class P
    {
        public IList<C> Children
        {
            get
            {
                Contract.Ensures(Contract.Result<IList<C>>() != null);
                Contract.Ensures(Contract.ForAll(Contract.Result<IList<C>>(), c => c != null));

                var result = new ReadOnlyObservableCollection<C>(new ObservableCollection<C>(new[] { new C() }));

                Contract.Assume(Contract.ForAll(result, c => c != null));

                return result; //CodeContracts: ensures unproven Contract.ForAll(Contract.Result<IList<C>>(), c => c != null)
            }
        }

        public class Program
        {
            public static int Main(string[] args)
            {
                foreach (var item in new P().Children)
                {
                    Contract.Assert(item == null); //CodeContracts: assert is false
                    Console.WriteLine(item.Length);
                }

                return 0;
            }
        }
    }
}

EDIT3:

Нашел хорошее краткое изложение проблем в http://social.msdn.microsoft.com/Forums/en-US/codecontracts/thread/af403bbc-ca4e-4546-8b7a-3fb3dba4bb4a/;, в основном, добавление дополнительных условий к контракту внедренного интерфейса нарушает принцип подстановки Лискова, потому что это означает, что класс с дополнительными ограничениями не может использоваться ни в каком контексте. который принимает объект, реализующий этот интерфейс.

0 голосов
/ 14 октября 2015

Обновите ObjectInvariant, чтобы включить проверки, чтобы гарантировать, что все элементы в коллекциях не равны NULL в конце каждого вызова метода.

  [ContractInvariantMethod]
  private void ObjectInvariant()
  {
    Contract.Invariant(_children != null && Contract.ForAll(_children, item => item != null));
    Contract.Invariant(_childrenReadOnly != null && Contract.ForAll(_childrenReadOnly, item => item != null);
  }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...