可空性分析无法对来自 NameValueCollection 的 null 发出警告。为什么?

Nullability analysis fails to warn on null coming from NameValueCollection. Why?

考虑在 .csproj 中启用可空性分析的 .net core 项目:

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>netcoreapp3.1</TargetFramework>
        <RootNamespace>(...)</RootNamespace>
        <IsPackable>false</IsPackable>
        <LangVersion>latest</LangVersion>
        <Nullable>enable</Nullable>
        <WarningsAsErrors>nullable</WarningsAsErrors>
    </PropertyGroup>
(...)

现在考虑这段代码:

public class NullabilityTests
{
    [Fact]
    public void Test()
    {
        var nameValueCollection = new NameValueCollection();
        string nullValue = nameValueCollection["I do not exist"];
        Foo(nullValue);
    }

    private void Foo(string shouldBeNotNull) => Assert.NotNull(shouldBeNotNull);
}

此代码编译和运行时没有警告,但测试在 Assert.NotNull 上失败。因此,可空性分析无法检测到空值,甚至无法警告 可能 空值。为什么?如果发生这种情况,我如何知道何时信任 C# 8 可空性分析?

您创建了一个字符串集合 - 字符串自然是可以为 null 的类型

您正在寻找一个不存在的项目,当您请求一个不存在的类型时,该类型将 return 为 null

Caution

The Get method does not distinguish between null which is returned because the specified key is not found and null which is returned because the value associated with the key is null.

https://docs.microsoft.com/en-us/dotnet/api/system.collections.specialized.namevaluecollection?view=netcore-3.1

NameValueCollection 类型尚未更新。来自 Introducing Nullable Reference Types in C#:

If they add anotations after you, however, then the situation is more annoying. Before they do, you will “wrongly” interpret some of their inputs and outputs as non-null. You’ll get warnings you didn’t “deserve”, and miss warnings you should have had. You may have to use ! in a few places, because you really do know better.

(强调我的)

特别是,该类型被声明为 return 类型 string,当启用可为 null 的引用类型时,这意味着引用不能为 null。编译器没有理由不这样想,所以不会发出警告。

由于您知道 null 是一个可能的 return 值,因此由您来强制执行。你可以通过简单地用正确的类型声明接收变量来做到这一点:

string? nullValue = (string?)nameValueCollection["I do not exist"];

(您需要显式转换,否则编译器的静态分析仍会将 now-nullable string? 变量视为 non-null。)

通常,解决这个问题的理想方法是使用更现代的类型。例如,您可以使用 Dictionary<string, string?> 作为等效的 nullable-aware 集合。但是,您注意到在这种情况下,您是从另一个 .NET 方法 (HttpUtility.ParseQueryString()) 取回集合的。

如果你想要更强大的保护,你可以立即将集合复制到 Dictionary<string, string?>,或者你可以为 NameValueCollection 类型实现一个包装器,用 more-correct 声明索引器string? return 类型。

前者的一个可能的例子是这样的:

var nameValueDictionary = nameValueCollection
    .Cast<string>()
    .ToDictionary(k => k, k => (string?)nameValueCollection[k]);