我应该同时使用NotNull和ContractAnnotation(" null => halt")?

时间:2014-06-01 21:16:40

标签: null resharper static-analysis design-by-contract

似乎NotNullContractAnnotation("key: null => halt")对R#的影响非常相似。有什么东西我错过了吗?我应该总是同时申请吗?

2 个答案:

答案 0 :(得分:8)

它们非常相似,但在语义上非常不同。

NotNull表示目标预计不会为空,但不会对结果做出任何陈述。可能存在抛出ArgumentNullException的空检查,或者可能只是在不检查的情况下使用它,并且您可以获得运行时NullReferenceException。同样,应用程序可能会检查null,记录并安全继续。

ContractAnnotation("key: null => halt")告诉ReSharper,如果key参数为null,则程序流程终止。 ReSharper可以像这样使用它:

string foo = null;
Assert.NotNull(foo);  // ContractAnnotation("null => halt")
Assert.Equal(12, foo.length);

此片段中的第二个断言将被标记为死代码,因为ReSharper知道第一个断言将在传递null时抛出。

但是,如果Assert.NotNull仅标有NotNull属性,则ReSharper会突出显示foo参数并发出警告,告诉您不应传递空值,但它不知道如果你这样做会发生什么。

微妙但不同。如果你要求值永远不为null,我会坚持使用NotNull属性,并使用ContractAnnotation("null => halt")作为断言样式方法,如果传递给null,它将明确地显式抛出。

答案 1 :(得分:5)

简而言之:

  • 通常,您只应使用[NotNull] / [CanBeNull]注释。如果您违反合同,R#将验证它们是否通知。它们通过接口实现和虚拟方法覆盖进行传播。它们很容易理解。

  • 如果您需要在指定方法行为方面更具表现力,可以使用[ContractAnnotation]。它不是[NotNull] / [CanBeNull]的替代品。它允许您链接方法输入和输出,链接notnull / null / true / false值,指定将使您成为方法halt的输入。您传递给[ContractAnnotation]的字符串称为"函数定义表",这只是方法体行为的抽象。

[ContractAnnotation] / [NotNull]在以下情况下表达不够时,通常需要使用[CanBeNull]

if (!IsNotNullAndAlsoValid(obj)) return; // null => false

Assert.That(x != null); // false => halt

if (!map.TryGetValue(key, out value)) { // => false, value: null
  ...                                   // + key parameter annotated as [NotNull]
}

是的,[NotNull]在语义上与[ContractAnnotation("null => halt")]不同。这很容易在代码中说明,有时候R#肯定知道执行会因为NRE而停止,并突出显示剩下的代码,因为'死了':

C c = null;
c.InstanceMethod(); // possible NRE
c.SomethingElse();  // unreachable code

但是当您使用[NotNull] - 带注释的API时,R#会警告您有关可能的NRE'。这样做是因为在R#没有足够的关于某些值不为空的信息的情况下,它不应该突出显示其他方法可能无法访问:

C c = AlwaysTrue ? new C() : null;
Util.NotNullAnnotatedMethod(c); // possible NRE
Util.OtherMethod(c);            // no warnings here
if (c != null) { ... }          // no warnings here

虽然[ContractAnnotation]允许您更严格地指定行为,但R#将更积极地依赖注释:

if (string.IsNullOrEmpty(text)) return; // text: null => false
if (text != null) { ... }               // condition always true
else { ... }                            // unreachable code