我正在与CodeContracts 静态分析工具争论。
我的代码:
(ASCII 版本)
该工具告诉我这instance.bar
可能是一个空引用。我相信相反。
谁是对的?我怎样才能证明它是错的?
CodeContracts 是对的。在调用该方法instance.bar = null
之前,没有什么可以阻止您进行设置。BarLength()
您的代码包含一个私有静态初始化实例:
private static Foo instance = new Foo();
您是否假设这意味着实例构造函数将始终在访问任何静态方法之前运行,因此确保bar
已初始化?
在单线程的情况下,我认为你是对的。
事件的顺序是:
Foo.BarLength()
Foo
(如果尚未完成)instance
具有实例的私有静态成员的静态初始化Foo
Foo.BarLength()
但是,每个应用程序域只触发一次类的静态初始化 - 并且 IIRC 没有阻塞以确保它在调用任何其他静态方法之前完成。
因此,您可能会遇到这种情况:
Foo.BarLength()
Foo
(如果尚未完成)开始Foo.BarLength()
Foo
因为这已经在进行中Foo.BarLength()
null
静态成员instance
Contracts 分析器无法知道您永远不会以多线程方式运行代码,因此必须谨慎行事。
更新:似乎问题是静态字段不支持不变量。
第二次更新:下面列出的方法是目前推荐的解决方案。
一种可能的解决方法是为您要保留的不变量instance
创建一个属性。Ensure
(当然,你需要Assume
他们Ensure
来证明。)一旦你这样做了,你就可以使用这个属性并且所有的不变量都应该被正确地证明。
这是您使用此方法的示例:
class Foo
{
private static readonly Foo instance = new Foo();
private readonly string bar;
public static Foo Instance
// workaround for not being able to put invariants on static fields
{
get
{
Contract.Ensures(Contract.Result<Foo>() != null);
Contract.Ensures(Contract.Result<Foo>().bar != null);
Contract.Assume(instance.bar != null);
return instance;
}
}
public Foo()
{
Contract.Ensures(bar != null);
bar = "Hello world!";
}
public static int BarLength()
{
Contract.Assert(Instance != null);
Contract.Assert(Instance.bar != null);
// both of these are proven ok
return Instance.bar.Length;
}
}
如果您将字段'bar'标记为只读,那应该满足静态分析器,即在ctor执行后该字段永远不会设置为其他任何内容。
我同意你的看法。 instance
并且bar
都是私有的,所以 CodeContracts 应该能够知道instance.bar
它永远不会设置为 null。