似乎NotNull
和ContractAnnotation("key: null => halt")
对R#的影响非常相似。有什么东西我错过了吗?我应该总是同时申请吗?
答案 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