我说的是这样的情况:在抛出警告时,没有可能的路径导致特定变量为空。类似的问题以前已经被问过很多次了,尤其是在这里,我发现一个问题在很大程度上突出了这个问题,甚至 C# 程序经理也做出了回应:
但是,我不明白给出的解释。或者更确切地说,他们确实谈论了缺乏复杂性,并且跟踪器仅跟踪变量内部值,而不跟踪它周围的程序流。我想提供一个我在 C# 中经常遇到的挫败感的例子,它不会导致例如以下问题: Java 根本:
public double DoSomething(MyClass obj)
{
if (obj is null) throw new ArgumentException("Object is null!");
var result = OtherMethodCall(obj.A);
// or alternative example:
var result = obj.A * 10 / 3;
return result;
}
在这里,C# 和 Java 编译器都没有问题 - 不会抛出有关可能从 null 对象访问 A 的警告,因为它们发现之前立即完成了 null 检查。但是,如果我将验证代码提取/重构为单独的方法,C# 将失去查看它的能力:
public double DoSomething(MyClass obj)
{
Validate(obj);
var result = OtherMethodCall(obj.A); // C# throws warning that A is possibly accessed from a null object
return result;
}
private void Validate(MyClass obj)
{
if (obj is null) throw new ArgumentException("Object is null!");
}
Java 编译器仍然可以看到变量之前的路径(显然,对于 Java 代码,我们必须在方法签名中添加“抛出”声明),而 C# 似乎不再看到该路径。如果我在这个类中有 2 个或多个方法需要相同的验证代码,而这些方法不够复杂,不足以保证将单独的验证器类添加为依赖项,它会“迫使”我重复自己,以免在我的代码中出现任何警告,或者添加大量丑陋的预处理器或其他语句来抑制警告。谁能向我解释为什么这在 C# 中仍然是一个问题?在上面的链接中,他们说这是有意的,但他们计划在 C# 9.0 中修复其中一些情况,但我正在使用 10.0。
对于“为什么”的问题,它不太好回答。世界上的时间和资源都是有限的,而人们对于用语言表达什么内容却有着无限的想法。一定有一些的事情还没有实现。
在今天的 C# 中,您可以将验证和异常抛出代码提取到如下方法中:
using System.Diagnostics.CodeAnalysis;
double DoSomething(object? obj)
{
if (!ValidateObject(obj)) ThrowCustomException(obj);
// this shows that obj.ToString produces no warning
var result = Convert.ToDouble(obj.ToString());
return result;
}
bool ValidateObject([NotNullWhen(true)] object? obj) =>
// assuming you are doing a lot more validation in the real code
obj is not null;
[DoesNotReturn]
void ThrowCustomException(object? obj) =>
// assuming that you are throwing a more "custom" exception in the real code
// possibly making use of obj
throw new NullReferenceException("obj is null!");
注意,验证和异常抛出需要放在单独的方法中,因为没有静态分析属性说“如果此参数为空,则该方法不返回”,以便您可以应用于您的版本
ValidateObject
这也会引发异常。只有 [DoesNotReturn]
(无条件)和 [DoesNotReturnIf]
(以 bool
参数为条件)。
[NotNullWhen]
告诉静态分析器参数不为空。这就是您得出上述解决方案的方式。