为什么我会收到这些奇怪的 C# 8“可空引用类型”警告:非空字段在退出构造函数之前必须包含非空值。?

互联网 更新时间:2023-04-26 20:59:54

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保护前置条件测试仍然是必要的。

更多推荐

我会,字段,函数,奇怪,类型

本文发布于:2023-04-26 20:59:52,感谢您对本站的认可!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:我会   字段   函数   奇怪   类型

发布评论

评论列表 (有 0 条评论)