C# 9 可空类型问题

6
考虑以下代码(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 
  }
}

我很难理解/解决F3中的错误,似乎编译器认为JohnAge是long而不是long?(在VS中悬停验证后)尽管Archive<T>.GetAt返回的是T?

是否有一种通用的存档方式可以实现我的需求(即返回可空类型的GetAt方法,即使T是非空基本类型,如long)?


1
我在你的另一个问题中发布了这个,我阅读了它并帮助我有点理解:https://dev59.com/-Lbna4cB1Zd3GeqPfbRB 所有答案都是相关的。 - Andy
2
如果您在每篇帖子中只提出一个问题(最好格式化,以便我们不需要横向滚动),那将会很有帮助。请问您在此帖子中实际想要回答哪个示例?请编辑问题,使其涉及该问题。您可以随时发布另一个问题来解决其他问题。 - Jon Skeet
@JonSkeet 好的,只需要按F3,已编辑。 - kofifus
2
@SandeepPandey -- 这就是我们业界所说的 拼凑 - Andy
2
好的,它在VS中编译是可以的,但是在命令行中不行。预览版的乐趣。我相信现在我可以添加一个答案了。 - Jon Skeet
显示剩余12条评论
1个回答

8
基本上,这归结于可空值类型和可空引用类型之间的巨大区别。CLR知道可空值类型,但就CLR而言,可空引用类型只是“普通引用类型,带有一个属性告诉编译器它是否应该被视为可空”。
当T具有notnull约束时,类型T?在IL中只编译为T。它必须这样做-它不能编译为Nullable,因为Nullable将T约束为值类型。
因此,对于Archive,如果字典中找不到键,则GetAt方法将返回0L-它不会(也不能)返回Nullable的空值,这实际上是您在F3中的代码所期望的。
整个“可空引用类型”特性受到了在根本上没有可空感知的类型系统上添加“面层”的尝试的困扰。我相信,如果现在正在从头开始设计新的运行时和语言,它将尝试更紧密地统一这一点。就现状而言,我认为该功能仍然具有很大的价值-但在泛型方面确实使事情变得非常棘手。

谢谢Jon!有没有办法让我得到我想要的?也就是一个返回T的GetAt方法?这样,如果我不检查null,我会得到一个警告,并且对于Manager和long也是一样的。 - kofifus
2
@kofifus:不,正如第二段所述。返回类型可以是TNullable<T>之一-不能同时存在,当T是引用类型时后者无效。这只是可空引用类型中令人烦恼的尖锐角落之一。不幸的是,你可能需要创建一个Archive<T>子类来约束Tstruct,并在那里创建一个新方法...但这也会有它自己的尖锐角落。 - Jon Skeet
关于您上面的解释(..当T具有notnull约束时..),我注意到即使我删除通用的notnull约束,我仍然得到完全相同的结果。 - kofifus
2
@kofifus:区别在于您将无法创建Archive<int?>Archive<string?>。我仍然很惊讶Archive<T>居然编译通过 - 这可能是预览版中的错误,或者是C# 9中NRT的更改。 - Jon Skeet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接