C#代码契约:什么可以静态证明,什么可以';T

C#代码契约:什么可以静态证明,什么可以';T,c#,static-analysis,code-contracts,proof,C#,Static Analysis,Code Contracts,Proof,我可能会说我对代码契约已经相当熟悉了:我已经阅读并理解了其中的大部分内容,并且已经使用了很长一段时间,但我仍然有一些问题。当我搜索“未经验证的代码契约”时,有相当多的点击,都在问为什么他们的特定语句不能被静态证明。尽管我也可以这样做,并发布我的特定场景(顺便说一句: ), 我更愿意理解为什么任何代码契约条件都可以或不能被证明。有时我对它能证明的东西印象深刻,有时我。。。好。。。礼貌地说:绝对没有印象。如果我想了解这一点,我想知道静态检查器使用的机制。我相信我会从经验中吸取教训,但我正在喷洒合同。

我可能会说我对代码契约已经相当熟悉了:我已经阅读并理解了其中的大部分内容,并且已经使用了很长一段时间,但我仍然有一些问题。当我搜索“未经验证的代码契约”时,有相当多的点击,都在问为什么他们的特定语句不能被静态证明。尽管我也可以这样做,并发布我的特定场景(顺便说一句:

),


我更愿意理解为什么任何代码契约条件都可以或不能被证明。有时我对它能证明的东西印象深刻,有时我。。。好。。。礼貌地说:绝对没有印象。如果我想了解这一点,我想知道静态检查器使用的机制。我相信我会从经验中吸取教训,但我正在喷洒
合同。假设到处都有
语句来消除警告,我觉得这不是代码合同的目的。谷歌搜索帮不了我,所以我想问问你们的经验:你们看到了什么(不明显的)模式?是什么让你看到了曙光?

简而言之,静态代码分析器似乎非常有限。例如,它不检测

readonly string name = "I'm never null";
作为一个不变量。从我在MSDN论坛上收集到的信息来看,它单独分析了每个方法(出于性能原因,而不是人们认为它可能会慢得多),这限制了它在验证代码时的知识

为了在证明正确性的学术崇高目标和能够完成工作之间取得平衡,我使用了

而不是在逻辑中散布大量假设。这可能不是使用CC的最佳实践,但它确实提供了一种在不取消选中任何静态检查器选项的情况下消除警告的方法。为了不丢失这些方法的前/后条件检查,我通常添加一个具有所需条件的存根,然后调用排除的方法来执行实际工作


我自己对代码契约的评估是,如果您只使用官方框架库,并且没有太多遗留代码(例如,在启动新项目时),那么这将非常好。任何其他的事情,都是一个快乐和痛苦的混合袋。

你的建筑合同没有得到满足。由于您正在引用对象的字段(this.data),因此其他线程可能可以访问该字段,并且可能会在假定和第一个参数解析以及第三个参数解析之间更改其值。(即,它们可以是三个完全不同的阵列。)

您应该将数组分配给一个局部变量,然后在整个方法中使用该变量。然后,分析器将知道约束得到满足,因为没有其他线程能够更改引用

var localData = this.data;
if (localData == null) return;
byte[] newData = new byte[localData.Length]; // Or whatever the datatype is.
Array.Copy(localData, newData, localData.Length); // Now, this cannot fail.
这样做的另一个好处是,不仅满足了约束,而且实际上,在许多情况下,使代码更加健壮

我希望这能引导你找到问题的答案。实际上,我无法直接回答您的问题,因为我无法访问包含静态检查器的Visual Studio版本。(我在VS2008 Pro上)我的答案是基于我自己对代码的目视检查得出的结论,而静态契约检查器似乎使用了类似的技术。我很感兴趣!我需要买一个-D

更新:(接下来有很多猜测)

经过深思熟虑,我认为我可以很好地猜测哪些是可以证明的,哪些是不能证明的(即使没有访问静态检查器)。如另一个答案所述,静态检查器不进行过程间分析。因此,随着多线程变量访问的可能性越来越大(如在OP中),静态检查器只能有效地处理局部变量(定义如下)

“局部变量”是指任何其他线程都无法访问的变量。这将包括在方法中声明或作为参数传递的任何变量,除非该参数用
ref
out
修饰,或者该变量在匿名方法中捕获

如果局部变量是值类型,则其字段也是局部变量(以此类推)

如果局部变量是引用类型,则只能将引用本身而不是其字段视为局部变量。即使是在方法中构造的对象也是如此,因为构造函数本身可能泄漏对构造对象的引用(例如,用于缓存的静态集合)

只要静态检查器不进行任何过程间分析,任何关于非局部变量的假设都可能在任何时候失效,因此在静态分析中被忽略

例外情况1:因为运行时知道字符串和数组是不可变的,所以只要字符串或数组变量本身是局部变量,它们的属性(如长度)就要进行分析。这不包括可由其他线程更改的数组内容

例外情况2:运行时可能知道数组构造函数不会泄漏对已构造数组的任何引用。因此,在方法体内部构造且未泄漏到方法外部(作为参数传递给另一个方法、分配给非局部变量等)的数组具有也可被视为局部变量的元素

这些限制似乎相当繁重,我可以想象有几种方法可以改进,但我不知道已经做了些什么。在理论上,这里还有一些可以用静态检查器完成的事情。随身携带的人员应检查已完成的工作和未完成的工作:

    它可以确定构造函数是否泄漏对象或其字段的任何引用,并考虑任何对象的字段,从而构造
    var localData = this.data;
    if (localData == null) return;
    byte[] newData = new byte[localData.Length]; // Or whatever the datatype is.
    Array.Copy(localData, newData, localData.Length); // Now, this cannot fail.