C# 代码契约是否未能发现Nullable和lt;T>;。HasValue和null?
我正在尝试将代码契约应用到我的代码中,我遇到了一个令人困惑的问题。 此代码无法满足合同要求,但除非我真的很厚,否则我希望它能够轻松分析C# 代码契约是否未能发现Nullable和lt;T>;。HasValue和null?,c#,code-contracts,static-code-analysis,C#,Code Contracts,Static Code Analysis,我正在尝试将代码契约应用到我的代码中,我遇到了一个令人困惑的问题。 此代码无法满足合同要求,但除非我真的很厚,否则我希望它能够轻松分析id在返回点必须有一个值 您可以尝试将字段复制到本地值,并根据该本地值编写语句。验证程序可能对字段持保守态度,因为调用可能会改变字段值。我已经弄清了这种行为的本质,这不是代码契约的错 我在中打开了生成的程序集,这是生成的代码: public Guid Id { get { Guid? guid = this.id;
id
在返回点必须有一个值
您可以尝试将字段复制到本地值,并根据该本地值编写语句。验证程序可能对字段持保守态度,因为调用可能会改变字段值。我已经弄清了这种行为的本质,这不是代码契约的错 我在中打开了生成的程序集,这是生成的代码:
public Guid Id
{
get
{
Guid? guid = this.id;
if (!guid.HasValue)
{
throw new InvalidOperationException();
}
guid = this.id;
return guid.Value;
}
}
实例变量id
被复制到局部变量,并且该局部变量在条件块后被重置回其原始值。现在很明显,为什么代码契约会显示一个违反契约的错误,但它仍然让我感到困惑,为什么代码会以这种形式重写。我做了更多的实验,并将代码契约从项目中完全删除,很明显这是标准的C#编译器行为,但为什么呢
这个秘密似乎是由于我在最初的问题中意外遗漏了一个小细节。id
实例变量被声明为readonly
,这似乎是导致编译器添加临时guid
变量的原因
我必须承认,我仍然感到困惑,为什么编译器认为它需要这样做以确保
id
的不变性,但我会继续挖掘…它不会将if-throw检查作为其契约的一部分。请尝试以下方法:
if (id == null)
throw new InvalidOperationException(string.Format("{0} '{1}' does not yet have an identity", typeof(T).Name, entity));
Contract.EndContractBlock();
您是否尝试过
!id.HasValue
?id字段的定义是什么?是否可能是只读的?请点击Sven很好的心理调试:)(见下文)如果你阅读C语言规范,你会发现值类型的readonly
字段不是变量。与属性一样,它们被视为值,这对于值类型意味着对它们的所有操作都必须在副本上完成。因此,就像访问值类型的属性一样,每次访问值类型的只读
字段时,它都会生成一个副本。事实上,我知道这是什么让我猜测,这是你的问题,基于反编译的代码。谢谢你的精彩解释。这就解释了这个谜!只是查了一下,相关的位在C#语言规范的§7.6.4中。这是我最初的想法,但“id”是这里的一个字段,因此可能不适合添加需求(代码的客户端可能无法直接确保满足该需求)。将其作为类不变量可能有意义(如果始终应设置“id”),但除此之外,这纯粹是一个状态检查,不应该有调用方要求。问题是您的if-then-throw实际上是一个合同,无论您的客户是否会影响检查结果。添加EndContractBlock唯一能做的就是让CC知道您的if-then-throw是一个合同,它是当前的合同ly不知道。这是真的。这可能是一个很好的提示,可以将此状态检查封装在一个可见属性中,这样客户端就可以通过将对象置于调用的正确状态来直观地知道它是否已完成其终止的契约。基于发现的问题,我相信这会起作用。通过显式创建复制时,它应该只创建一次副本,而不是两次(在隐式只读情况下)
if (id == null)
throw new InvalidOperationException(string.Format("{0} '{1}' does not yet have an identity", typeof(T).Name, entity));
Contract.EndContractBlock();