C# 9 可空类型问题

C# 9 Nullable types issues

考虑以下(VS 16.8.0 预览版 2.1 C# 9.0 预览版)代码:

#nullable enable

using System.Collections.Generic;

class Archive<T> where T : notnull 
{
  readonly Dictionary<string, T> Dict = new();

  public T? GetAt(string key) 
  { 
    return Dict.TryGetValue(key, out var value) ? value : default;
  }
}

class Manager 
{
  public int Age { get; set; }
}

class Main34 
{
  long F3() 
  {
    Archive<long> a = new();
    var johnAge = a.GetAt("john");
    if (johnAge is null) return -1; // Error CS0037  Cannot convert null to 'long' because it is a non - nullable value type
    return johnAge; 
  }

  long F4() 
  {
    Archive<Manager> a = new();
    var johnAge = a.GetAt("john");
    //if (johnAge is null) return -1;
    return johnAge.Age; // Correct ! warning "Derefrencing of a possibly null reference" will be removed if line above unremarked 
  }
}

我很难过 understanding/addressing F3 中的错误, 似乎编译器认为 johnAge 有 long 而不是 long?(正如我通过在 VS 中将鼠标悬停在它上面验证的那样)尽管 Archive<T>.GetAt 的 return 是 T?

有没有办法拥有一个通用的存档来做我想做的事情(一个 GetAt 方法 return 即使 T 是不可为空的基本类型,即 long 也可以为空)?

从根本上说,这归结为可为 null 的值类型和可为 null 的引用类型非常非常不同。 CLR 知道可空值类型,但就 CLR 而言,可空引用类型只是“普通引用类型,带有一个属性告诉编译器是否应将其视为可空”。

T 具有 notnull 约束时,类型 T? 仅在 IL 中编译为 T。它必须 - 它不能编译为 Nullable<T>,因为 Nullable<T> 约束 T 是一个值类型。

因此对于 Archive<long>,如果在字典中找不到键,GetAt 方法将 return 0L - 它 不会 (并且不能)return Nullable<long> 的空值,这是您在 F3 中的代码有效期望的值。

整个“可空引用类型”功能的缺点是试图在一个根本没有它的类型系统上添加一个可空意识的“伪装”。我敢肯定,如果现在从头开始一起设计一种新的运行时和语言,它会尝试更紧密地统一这一点。事实上,我相信该功能仍然具有很大的价值 - 但它确实使事情 真的 在泛型方面变得棘手。