pin*_*x33 5
所有这些场景对我来说都很有意义。让我们看看每一个,但首先让我们就一些事情达成一致:
不可为空/可为空的引用类型不是真正的类型。它们只是编译器知道如何解释的注释
该结构被限制为notnull
但是有人可以传递null
对它的引用,如果他们没有启用可空分析(#nullable enable
),则不会发出任何警告
val is {}
是一个null
测试。通过使用它,您暗示该值可以是null
编译器使用流分析。它寻找各种模式来确定值的状态。它将把你的话,有些事情是/不是null
,它会用以前的任务为变量的空性的证据,即使它可能没有什么意义
在大多数(所有?)情况下,流分析不会向后工作,这意味着在线断言状态N
不会影响线N-1
让我们看一下例子:
public struct ThingA<T> where T : notnull
{
private readonly bool _hasValue;
private readonly T _value;
ThingA(T value)
{
_value = value;
_hasValue = value is { };
}
}
ThingA
不发出警告。您已约束notnull
并分配给_value
. 在此之前,您还没有执行任何可空性断言,因此编译器假定到目前为止该参数实际上不是null
每个 contract。流分析不会向后工作。分配给_hasValue
使用null
测试对参数只会影响未来的可为空的状态value
参数。编译器不会说“嘿,我记得value
用来分配某些东西,让我去修复所有东西”——那样工作太多了。现在我们退出构造函数并且您没有重新分配该_value
字段,因此其先前确定的“非空”状态仍然存在。没有警告。
public struct ThingB<T> where T : notnull
{
private readonly bool _hasValue;
private readonly T _value;
ThingB(T value)
{
_value = value;
_hasValue = _value is { };
}
}
ThingB
从与 类似的流程开始ThingA
。因为我们没有以前的null
测试并且我们notnull
对 T有约束,所以分配给该_value
字段会根据合同加强其非空状态。但这就是它变得棘手的地方!您现在执行null
对非常相同的现场测试,从而暗示它可能实际上是null
。流分析不会向后工作,因此我们不会在分配给该字段的行上收到警告,但您已告诉编译器该字段的状态可能是null
. 我们正在退出带有可能null
字段的构造函数。这是你的警告。
public struct ThingC<T> where T : notnull
{
private readonly bool _hasValue;
private readonly T _value;
ThingC(T value)
{
_hasValue = value is { };
_value = value;
}
}
对于ThingC
,您null
对参数执行检查,暗示它可以是null
。参数的null
状态更改为“可能为空”,然后在分配给 时使用该参数_value
。嗯,你刚才说的它可以是null
,但每个约束它不能。这是第一个警告。现在我们离开构造函数体,字段的状态仍然是“可能为空”(根据赋值)。这是第二个警告。
在你说的评论中
即使我检查 null 值并在赋值之前抛出 ArgumentNullException 也是如此。
好吧,这取决于您将支票放在哪里。考虑一下:
public struct ThingD<T> where T : notnull
{
private readonly bool _hasValue;
private readonly T _value;
ThingD(T value)
{
if (value is null) throw new Exception();
_hasValue = value is { };
_value = value;
}
}
在正常情况下,流分析会捕捉到这一点。除本遭受了同样的问题ThingC
:你告诉编译器,这个参数还是(!不知)可以是null
你的_hasValue
任务/null
测试。你会得到同样的警告。夏普实验室
如果在测试后移动null
-check plus 异常,则不会收到警告:_hasValue
public struct ThingE<T> where T : notnull
{
private readonly bool _hasValue;
private readonly T _value;
ThingE(T value)
{
_hasValue = value is { };
if (value is null) throw new Exception();
_value = value;
}
}
处理时ThingE
,没有警告。异常保护对_value
已断言参数 is not的字段的赋值null
。尽管_hasValue
赋值暗示它可以。你是说“从这一点上它绝对不是null
”。夏普实验室
请记住,可为空的注释和约束不是类型系统的“真实”部分。传递可空值甚至null
直接传递都不会产生编译错误,只会产生警告(当然除非TreatWarningsAsErrors
启用)。还请记住,您的期望非null
参数的类/方法可能会被欺骗,无论是通过
#nullable
使用null
-fiving 运算符,!
忽略警告(根据我的经验,很多人都这样做)
您仍然必须后卫null
,通常检查和投掷右远。这将有助于进行大量的流量分析。流量分析并不完美。有很多模式应该可以检测到但不能检测,并且团队一直在努力使其更好(C#9 有一些改进)。
有趣的是,当可空引用类型第一次出现时,我真的不确定如果我们使用非类型是否真的需要执行null
测试null
。2019 年,在他的一次 C#8 演讲之后,我在 Microsoft Ignite 上与 Mads Tersen 进行了一次相当长的对话。他同意我的看法,即这个主题可能不清楚,而且(至少在当时)甚至文件也没有让它变得明显。他强调,除非类型是内部的——所以,作为公共 API 的一部分——否则执行null
保护前置条件测试仍然是必要的。
更多推荐
我会,字段,函数,奇怪,类型
发布评论