C# 9 可空类型问题

kof*_*fus 6 c# c#-8.0 c#-9.0

考虑以下(VS 16.8.0 Preview 2.1 C# 9.0 preview)代码:

#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 
  }
}
Run Code Online (Sandbox Code Playgroud)

我有一个很难理解/解决F3中的错误,好像编译器认为johnAge有long没有long?(因为我验证通过将鼠标悬停它VS),尽管回归Archive<T>.GetAt幸福T?

有没有办法让通用存档可以做我想做的事情(即使 T 是不可为空的基本类型,即 long,也返回 Nullable 的 GetAt 方法)?

Jon*_*eet 8

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

Tnotnull约束时,类型T?就编译到TIL 中。它必须 - 它不能编译为Nullable<T>,因为Nullable<T>约束T是值类型。

因此,对于 an Archive<long>GetAt如果在字典中找不到键,该方法将返回 0L - 它不会(也不能)返回 a 的空值Nullable<long>,这正是您的代码F3所期望的。

整个“可为空引用类型”功能受到的影响是试图在基本上没有它的类型系统上添加可空感知的“贴面”。我敢肯定,如果现在从头开始设计新的运行时和语言,它会尝试更紧密地统一这一点。事实上,我相信这个特性仍然有很多价值——但是当涉及到泛型时,它肯定会让事情变得非常棘手。

  • @kofifus:不,如第二段所述。返回类型可以是 `T` 或 `Nullable&lt;T&gt;` - 不能同时是两者,并且当 `T` 是引用类型时,后者无效。不幸的是,这只是可空引用类型令人讨厌的尖角之一。您可能会创建一个“Archive&lt;T&gt;”的*子类*,将“T”限制为“struct”,并在那里创建一个新方法......但这会有自己的尖角。 (2认同)
  • @kofifus:不同之处在于,您将无法创建“Archive&lt;int?&gt;”或“Archive&lt;string?&gt;”。我仍然对“Archive&lt;T&gt;”能够编译感到惊讶 - 这要么是预览中的错误,要么是对 C# 9 中 NRT 的更改。 (2认同)