C# 可空引用类型:此函数是否可以在不覆盖空检查的情况下接受具有可空值和不可空值的字典?

C# Nullable Reference Types: can this function accept a dictionary with both nullable and non-nullable value without overriding null checking?

我正在尝试编写一个函数,将字典的 TryGet 转换为 C# 9.0 中的“查找”对象。到目前为止,我的(简化版)代码如下所示:

class C1
{
    [Test]
    public void METHOD()
    {
        var lookupValue1 = Lookup(new Dictionary<string, string?>(), "a");
        var lookupValue2 = Lookup(new Dictionary<string, string>(), "a");
    }

    public LookupValue<TV> Lookup<TK, TV>(Dictionary<TK, TV?> d, TK key) 
        where TK : notnull
        where TV : notnull
    {
        if (d.TryGetValue(key, out var result))
        {
            return new LookupValue<TV>(result);
        }
        else
        {
            return new LookupValue<TV>(default);
        }
    }

    public record LookupValue<T>(T? Value) where T : notnull;
}

但是这段代码不会编译,特别是这一行:var lookupValue2 = Lookup(new Dictionary<string, string>(), "a");

我知道我可以使用 ! 运算符来覆盖空引用检查:var lookupValue2 = Lookup(new Dictionary<string, string>()!, "a"); 但这应该是 public API 我儿子我不希望 API 的用户必须这样做。另外,我知道我可以在同一个 class 中创建两个名称不同的方法,或者在不同的 class 中创建名称相同的方法,但我想尽可能避免这种情况。

有没有办法让这个 Lookup 函数接受具有可为空值类型和具有非可为空值类型的字典并保留 LookupValue 记录的签名?

您编写了一个函数,它需要一个字典,其值可以为空。换句话说,一个键的值可能为空,并且允许为一个键设置一个空值。因此,这样的函数没有合理的方法来接受其值不可为 null 的字典。

相反,您可以考虑让函数接受具有 any 种值类型的字典。您可以通过删除 TV 上的 notnull 约束并删除字典类型的类型参数中的可为空注释来实现。

SharpLab

class C1
{
    [Test]
    public void METHOD()
    {
        var lookupValue1 = Lookup(new Dictionary<string, string?>(), "a");
        var lookupValue2 = Lookup(new Dictionary<string, string>(), "a");
    }

    public LookupValue<TV?> Lookup<TK, TV>(Dictionary<TK, TV> d, TK key) 
        where TK : notnull
    {
        if (d.TryGetValue(key, out var result))
        {
            return new LookupValue<TV?>(result);
        }
        else
        {
            return new LookupValue<TV?>(default);
        }
    }

    public record LookupValue<T>(T Value);
}

以下有效,但需要重载、另一个通用参数(无法推断)和代码中的强制转换。尽管如此,如果可空用例很少见,这有时可能比另一种名称不同的方法更好。

class C1
{
  [Test]
  public void Method()
  {
    var lookup1 = new Dictionary<string, string?>().Lookup("a");
    var lookup2 = new Dictionary<string, string>().Lookup<string, string, string>("a");
  }
}

public static class DictExtensions
{
  public static LookupValue<TV> Lookup<TK, TV>(this IDictionary<TK, TV> d, TK key)
    where TK : notnull
    where TV : notnull
  {
    return d.Lookup<TK, TV, TV>(key);
  }

  public static LookupValue<TR> Lookup<TK, TV, TR>(this IDictionary<TK, TV> d, TK key)
    where TK : notnull
    where TR : notnull, TV
  {
    if (d.TryGetValue(key, out var result))
    {
      return new LookupValue<TR>((TR?)result);
    }
    else
    {
      return new LookupValue<TR>(default);
    }
  }

  public record LookupValue<T>(T? Value) where T : notnull;
}