【问题标题】:C# 8+ Nullable Reference checking: Can Static Analysis work out that either this field or that field is non-null?C# 8+ 可空引用检查:静态分析能否确定该字段或该字段是否为非空?
【发布时间】:2021-09-27 14:47:53
【问题描述】:

考虑一个有 2 个构造函数的类。假设一个构造函数接受一个依赖项,而另一个接受一个工厂以便稍后获得依赖项:

class ThereCanBeOnlyOneButAtLeastOne
{
    readonly Thing? knownThing;
    readonly ThingFactory? factory;
    public ThereCanBeOnlyOneButAtLeastOne(ThingFactory factory) => this.factory = factory;
    public ThereCanBeOnlyOneButAtLeastOne(Thing knownThing) => this.knownThing = knownThing;

    public OtherThing CalculateSomething()
    {
        var theThing = knownThing??factory?.Get();

我希望静态分析能够确定此时 theThing 被保证为 NotNull,因为必须至少调用了一个构造函数。但事实并非如此。

可以吗?

【问题讨论】:

  • 注意theThing的类型依赖于工厂的Get方法的返回类型,请确保它返回一个不可为空的类型。
  • 确实如此。否则我的问题会有缺陷:-)
  • 不能指望静态分析深入这个兔子洞。这是一种对我们来说听起来“合理”但在实践中需要不平凡的规则的事情。我认为解决这个问题的一种简单且优雅的方法是始终只有ThingFactory,并使用始终返回knownThingSameThingFactory(或者如果整个类过于繁重,则将两者封装为委托)。当然还有其他统一逻辑的方法,至少需要一个 null 覆盖,但如果有必要,只需要一次。
  • 请注意,仅使用factory?.Get() 就足以使静态分析认为您必须比它更了解可空性,因为它会假设结果可能是null,即使使用不可为空的factory 甚至当knownThing 根本不涉及。覆盖方法是knownThing ?? factory!.Get(),因为“我们”知道如果knownThingnull,则factory 一定不是(这也是为什么在这里使用?. 可能是不合适的)。但是请注意,这个假设很脆弱,如果我们添加另一个构造函数,很容易失败。
  • 委托方法将是readonly Func<Thing> getThing,分配为() => knownThingfactory.Get。然后knownThingfactory 字段都成为可选的,删除它们将强制方法使用getThing(而不是在应该使用另一个时无意中使用一个)。当然,保留它们可能是有原因的(您需要将它们传递下去,或者对于单个对象而不是工厂确实有单独的逻辑)。

标签: c# c#-8.0


【解决方案1】:

2021 年的共识似乎是:不,不能,你必须告诉它。

C# 9 中有一些几乎相关的功能:

    [MemberNotNullWhen(true, nameof(knownThing))]
    [MemberNotNullWhen(false, nameof(factory))]
    public bool HasKnownThing => knownThing != null;

这可能会让你认为

var theThing = HasKnownThing ? knownThing : factory.Get();

可证明是非空的,但静态分析表明它并不真的知道工厂是非空的:

Scratch.cs(38, 38): [CS8775] Member 'factory' must have a non-null value when exiting with 'false'

无论你如何扭曲,它仍然归结为:

静态分析无法自行确定至少有一个字段不为空,您必须告诉它。 告诉的最小方法可能是:

theThing = knownThing ?? factory!.Get()

是否在变量或字段或私有属性或方法中执行此操作。

【讨论】:

  • MemberNotNullWhen 的有趣方法。事实上,这在我的安装中确实有效(即[MemberNotNullWhen(false, nameof(factory))],另一个不是必需的/不添加任何东西,因为条件已经建立)。
  • Demo.
  • :-) 复制粘贴您的演示 我仍然收到[CS8775] Member 'factory' must have a non-null value when exiting with 'false'. 警告?所以在这一点上我觉得我可以忍受,它做了很多但不是(还)一切。
  • 这很奇怪,因为通常 Sharplab 具有最新版本的编译器(并且它不会发出警告),并在我的 Visual Studio 中本地尝试(针对 .NET 5,而不是预览版)我得到也没有警告。也许你在分析仪有这个额外规则的前沿更进一步。
猜你喜欢
  • 1970-01-01
  • 2012-02-21
  • 2011-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-13
  • 1970-01-01
相关资源
最近更新 更多