我有一个可以为null的上下文中的C#代码:
public string? GetValue(int key, bool errorIfInvalidKey)
{
string result = <get the value identified by the key argument, or null if not found>;
if (result == null && errorIfInvalidKey) {
throw new InvalidOperationException("Bad key");
} else {
return result;
}
}
如果调用方指定了无效的key
,则errorIfInvalidKey
参数指定是返回null还是引发异常。因此,如果errorIfInvalidKey
为true,则保证此代码返回非null。
有没有一种方法可以注释这段代码,告诉编译器如果参数包含特定值,那么返回可能为null的例程将返回非null?
编译器在方法边界处停止。方法的签名是编译器所依赖的约定。例如,如果方法及其调用方位于两个不同的程序集中,则当程序集被不同的版本替换时,函数的实现可能会发生更改,而调用方不会意识到这一点。这就是为什么编译器必须依赖方法的签名才能正确,而不需要查看其实现来获得更多信息的原因。
当行为上的差异如此根本不同(返回null或抛出异常(时,请使用两个不同版本的方法,而不是试图通过参数来控制它。
调用该方法的两个不同变体的代码和编写该代码的程序员无论如何都必须事先知道会发生什么(异常或空值(,这样它就可以决定使用哪种方法了。
public string? GetValueOrNull(int key)
{
string? result = <get the value identified by the key argument, or null if not found>;
return result;
}
public string GetValue(int key)
{
string? result = <get the value identified by the key argument, or null if not found>;
if (result == null) {
throw new InvalidOperationException("Bad key");
} else {
return result;
}
}
有许多属性可以用来向编译器声明方法的内部逻辑是如何工作的,这样编译器就可以根据一些其他条件来假设变量是否为空:
由C#编译器解释的空状态静态分析的属性
但是,这些都不支持您的场景。但是您可以使用NotNullWhen
属性将上面的GetValueOrNull
方法替换为TryGet。。。图案:
public bool TryGetValue(int key, [NotNullWhen(true)] out string? value)
{
value = <get the value identified by the key argument, or null if not found>;
return value != null;
}
然后可以这样使用:
public void TestSomething()
{
if(TryGetValue(42, out string? value))
{
// No warning here
Console.WriteLine(value.Length);
}
// Warning: Dereference of a possibly null reference
Console.WriteLine(value.Length);
}
有没有一种方法可以注释这段代码,告诉编译器如果参数包含特定值,那么返回可能为null的例程将返回非null?
不,至少目前没有这样的事情。最接近的是DoesNotReturnIfAttribute
,但在调用具有相应值的方法后,它将停止所有的null检查。I.e:
string? GetValue(int key, [DoesNotReturnIf(true)] bool errorIfInvalidKey)
{
// ...
}
var v1 = GetValue(2, false);
Console.WriteLine(v1.GetHashCode()); // warning
var v2 = GetValue(1, true);
Console.WriteLine(v2.GetHashCode()); // no warning
Console.WriteLine(v1.GetHashCode()); // no warning !!!
我认为一分为二的方法不仅解决了问题,而且提高了代码的可读性:
string? GetValue(int key)
{
// ...
}
string GetValueOrThrow(int key) => GetValue(key) ?? new InvalidOperationException("Bad key");
而且用法要明显得多——GetValueOrThrow(1)
比GetValue(1, true)
读起来好得多。