CodeContracts与集合类型

时间:2011-11-25 17:15:21

标签: c# c#-4.0 collections code-contracts

我班上有一些子项目,我有一个公共访问者。我想提供一个后置条件来确保集合中的项不为null(我知道,在测试2和3中,调用者可以更改集合,但是现在我的目标只是确保,从属性返回的集合没有不包含 null 项目。)

我认为使用Assume和ForAll就够了,但这没有帮助

以下是我尝试过的3个类的示例代码。 除了第一次暴露 ReadOnlyObservableCollection ,第二次 - 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 个答案:

答案 0 :(得分:1)

我会创建自己的集合类型。例如,您可以实现IList<T>并“确保”索引getter永远不会返回null,并且“require”表示Add()并且索引setter永远不会将null作为参数。

编辑:

为了避免在foreach循环中“可能在空引用上调用方法”消息,您可能还必须实现自己的枚举器类型并“确保”其Current属性永远不会返回null。 / p>

EDIT2:

由于ObservableCollection<>ReadOnlyObservableCollection<>都装饰了IList<>实例并因此实现了IList<>,因此我尝试了以下操作。请注意“确保未经证实”和“断言为假”之间的不一致。无论静态类型resultReadOnlyObservableCollection<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/找到了一个很好的问题摘要;基本上,为已实现接口的合同添加附加条件违反了Liskov替换原则,因为这意味着具有附加限制的类不能在接受实现该接口的对象的任何上下文中使用。

答案 1 :(得分:1)

我试图采用相同的方法,而且我进入的大多数是:

  1. 声明通用的INonNullable&lt; T&gt;只有一个属性的接口确保返回非null值,在NonNullable struct中实现它。
  2. 声明INonNullEnumerator&lt; T&gt;和INonNullEnumerable&lt; T&gt;接口(很可能是没用的),与IEnumerator和IEnumerable相同,但是INonNullEnumerable具有IsEmpty属性,而GetEnumerator要求它为false。 NonNullEnumerator返回T,而不是INonNullable&lt; T&gt;。
  3. 声明我的自定义集合实现INonNullEnumerable&lt; T&gt;和IList&lt; INonNullable&lt; T&gt;&gt; (为了兼容常见的IEnumerable和常识),基于NonNullable数组。 IList的方法使用INonNullable参数显式实现,但是使用相同的隐式方法接受带有契约的T值。
  4. 因此,这个hydra可以作为通常的IEnumerable参数传递,返回INonNullable值(静态检查器仍然需要检查空值,因为它是引用类型),而T值具有非null保证可以在方法和foreach语句中使用(因为foreach使用隐式GetEnumerator()方法,它返回INonNullEnumerator,它确保返回非空的INonNullable,它是NonNullable结构,并且所有这些都由契约支持)。

    但老实说,这是一个怪物。我编写它只是为了尽力使合同保证集合没有空值。然而,没有取得圆满成功:Contract.ForAll(myList,item =&gt; item!= null)无法证明,因为它使用IEnumerable,既不是foreach也不是我的INonNullEnumerable。

    我敢打赌,这是不可能的,至少目前的CodeContracts API。

答案 2 :(得分:0)

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