假设我有一个方法public void Foo(string bar)
,调用者不应该调用空值bar
。假设我还有一个方法,称之为private void FooImpl(string bar)
,它可以完成Foo
的实际工作。当然,FooImpl
确实要求bar
的非空,即使Foo
是公共接口。并且假设我想使用.NET 4.0代码契约强制执行此非null。
我在哪里签合同?
如果我这样做:
public void Foo(string bar)
{
this.FooImpl(bar);
}
private void FooImpl(string bar);
{
Contract.Requires<ArgumentNullException>(bar != null);
// Something that requires non-nullness, e.g.:
bar.Contains("test");
}
然后静态检查器抱怨Foo
正在使用可能为空的值调用FooImpl
,并建议我将非空合约添加到Foo
。好的,所以我想我不能将我的合同检查/异常抛出委托给实现方法。
但如果我试图把它放在公共界面,即:
public void Foo(string bar)
{
Contract.Requires<ArgumentNullException>(bar != null);
this.FooImpl(bar);
}
private void FooImpl(string bar);
{
bar.Contains("test");
}
然后静态检查器抱怨FooImpl
在可能为空的值上调用Contains
---即使代码中FooImpl
被调用的唯一地方来自Foo
,它本身确保永远不会使用空值调用FooImpl
。
那么,我需要两次包含相同的合同吗?或者我应该忽略静态检查器?我知道这是繁忙工作的一种来源,不应该依赖,但我希望它有一些方法来处理这个基本的,可能是常见的情景。
答案 0 :(得分:3)
简短回答:是的。
您应该在希望代码约定的任何地方添加前置条件,以防止出现无引用异常等问题。这有时意味着看起来像你要两次添加相同的合同。
在这种特殊情况下,很明显,FooImpl仅从已经具有前置条件的方法调用。
但是,静态检查器独立于其他方法评估FooImpl方法。 在这个例子中,你只需从Foo传递条形值(你知道bar不是null),但是静态检查器不能确定你没有操纵bar,可能导致它为null。
此外,您应该考虑将来可能从没有前提条件的方法调用FooImpl方法来检查bar是否为null。您希望静态检查程序可以防止在这些情况下发生空引用异常。
答案 1 :(得分:1)
为了安抚静态检查器,我可能在内部函数上使用Contract.Assume(),而不是正常的契约函数,所以它不是现有契约的精确副本 - 你不想要或者在内部函数中需要实际的运行时检查。
至于为什么它不够聪明地推断出这个功能只能从另一个地方调用,我不确定。