【问题标题】:C# Nullable Reference Types: can this function accept a dictionary with both nullable and non-nullable value without overriding null checking?C# Nullable 引用类型:此函数是否可以在不覆盖 null 检查的情况下接受具有可为 null 和不可为 null 值的字典?
【发布时间】:2026-01-29 17:45:01
【问题描述】:

我正在尝试编写一个函数,它将字典的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&lt;string, string&gt;(), "a");

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

有什么方法可以让这个Lookup 函数接受具有可空值类型的字典以及具有不可空值类型的字典并保留LookupValue 记录的签名?

【问题讨论】:

  • 不幸的是,c# 一直在挖掘null hole even deeper。您真正需要的是一种描述optional type 并展开它的方法。
  • 这种问题是由可空引用类型引入的。在 C#8 中它不会存在。
  • 你不需要你的LookupValue 类型:它是多余的。为什么你认为你需要它? (而且你最终会让人们对 Linq 的 ILookup 感到困惑,这完全是另外一回事)。
  • 您的Lookup 方法应该接受IReadOnlyDictionary,而不是Dictionary
  • TV 是一个结构时,return new LookupValue&lt;TV&gt;(default); 这样做是一个非常糟糕的主意,因为没有迹象表明查找失败并且该值毫无意义。跨度>

标签: c# nullable-reference-types c#-9.0


【解决方案1】:

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

相反,您可以考虑让函数接受具有 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);
}

【讨论】:

  • 谢谢。您的回答解释了为什么无法得到我想要的东西。我不想删除notnull 约束,所以我的第二个最佳选择是创建两个不同命名的方法——一个接受Dictionary&lt;T, V&gt;,另一个接受Dictionary&lt;T,V?&gt;
【解决方案2】:

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

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;
}

【讨论】:

    最近更新 更多