C# 具有集合类型的CodeContracts
我的类中有一个子项集合,并且有一个公共访问器。我想提供一个后置条件来确保集合中的项不为null(我知道,在测试2和3中,调用方可以更改集合,但现在我的目标只是确保从属性返回的集合不包含null项) 我认为使用假设和ForAll就足够了,但那没有帮助 下面是我尝试过的3个类的示例代码。 除第一次公开ReadOnlyObservableCollection、第二次公开ObservableCollection和第三次公开列表外,所有3种情况都是最相同的 -ReadOnlyObservableCollectionC# 具有集合类型的CodeContracts,c#,c#-4.0,collections,code-contracts,C#,C# 4.0,Collections,Code Contracts,我的类中有一个子项集合,并且有一个公共访问器。我想提供一个后置条件来确保集合中的项不为null(我知道,在测试2和3中,调用方可以更改集合,但现在我的目标只是确保从属性返回的集合不包含null项) 我认为使用假设和ForAll就足够了,但那没有帮助 下面是我尝试过的3个类的示例代码。 除第一次公开ReadOnlyObservableCollection、第二次公开ObservableCollection和第三次公开列表外,所有3种情况都是最相同的 -ReadOnlyObservableColle
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);
}
}
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);
}
}
为了避免每次使用Children
属性时都编写contract.aspect(child!=null)
,我是否可以定义一个contract
更新: 我尝试实现
枚举器
,以确保Current
属性getter中的条件不为空,正如phoog所建议的那样。但警告仍然存在(令我惊讶)
公共类NotNullEnumerable:IEnumerable
{
私有IEnumerable enumerable;
公共NotNullEnumerable(IEnumerable enumerable)
{
_可枚举=可枚举;
}
#区域可数成员
公共IEnumerator GetEnumerator()
{
返回新的NotNullEnumerator(_enumerable.GetEnumerator());
}
#端区
#区域可数成员
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
返回GetEnumerator();
}
#端区
}
公共类NotNullEnumerator:IEnumerator
{
私有只读IEnumerator\u枚举器;
公共NotNullEnumerator(IEnumerator枚举器)
{
_枚举数=枚举数;
}
#区域IEnumerator成员
公共电流
{
得到
{
Contract.sure(Contract.Result()!=null);
返回_enumerator.Current;
}
}
#端区
#区域IDisposable成员
公共空间处置()
{
_枚举数。Dispose();
}
#端区
#区域IEnumerator成员
对象System.Collections.IEnumerator.Current
{
得到
{
Contract.sure(Contract.Result()!=null);
返回_enumerator.Current;
}
}
公共图书馆
{
返回_枚举数.MoveNext();
}
公共无效重置()
{
_枚举数。重置();
}
#端区
}
代码中的用法:
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'
}
test1t1=newtest1();
var NonNullTest1=新的NotNullEnumerable(t1.Children);
foreach(非NullTest1中的子级)
{
child.SomeMethod();//CodeContracts:可能对空引用“child”调用方法
}
有什么想法吗?我会创建自己的收藏类型。例如,您可以实现
IList
,并“确保”索引getter从不返回null,“要求”索引getter从不将Add()
和索引setter作为参数接收null
编辑:
为了避免foreach循环中出现“可能在null引用上调用方法”消息,您可能还必须实现自己的枚举器类型,并“确保”其当前
属性从不返回null
编辑2:
由于observeCollection
和ReadOnlyObservableCollection
都装饰了IList
实例,因此实现了IList
,我尝试了以下方法。注意“确保未经证实”和“断言为假”之间的不一致性。无论result
的静态类型是ReadOnlyObservableCollection
还是IList
,我都收到了相同的消息。我正在使用代码契约版本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;
}
}
}
}
命名空间EnumerableContract
{
公共C类
{
公共整数长度{get;私有集;}
}
公共P类
{
公营儿童
{
得到
{
Contract.sure(Contract.Result()!=null);
Contract.assures(Contract.ForAll(Contract.Result(),c=>c!=null));
var result=new ReadOnlyObservableCollection(new ObservableCollection(new[]{new C()}));
假设(Contract.ForAll(result,c=>c!=null));
返回结果;//CodeContracts:确保未经验证的Contract.ForAll(Contract.result(),c=>c!=null)
}
}
公共课程
{
公共静态int Main(字符串[]args)
{
foreach(新P()子项中的变量项)
{
Assert(item==null);//CodeContracts:Assert为false
控制台写入线(项目长度);
}
返回0;
}
}
}
}
编辑3:
发现了一个很好的问题总结在;基本上,将附加条件添加到已实现接口的契约中违反了Liskov替换原则,因为这意味着具有附加限制的类不能用于接受实现该接口的对象的任何上下文中。我尝试了相同的方法,我遇到的最多的是:
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'
}
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;
}
}
}
}
[ContractInvariantMethod]
private void ObjectInvariant()
{
Contract.Invariant(_children != null && Contract.ForAll(_children, item => item != null));
Contract.Invariant(_childrenReadOnly != null && Contract.ForAll(_childrenReadOnly, item => item != null);
}