可空引用类型和ToString()重载

5
请注意,本问题涉及最新的C# 8 可空引用类型, 我已经在csproj文件中启用了它,方法是通过以下<Nullable>enable</Nullable>声明。
考虑以下简单的代码。
class SortedList<T> where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable
{
    private Node? _node;
    private readonly IComparer<T> _comparer = Comparer<T>.Default;

    class Node
    {
        public Node(T value)
        {
            Value = value;
        }

        public T Value { get; }
        public Node? Next { get; set; }

        public override string ToString()
        {
            return Value.ToString();
        }
    }

    //rest of code, that isn't important
}

这行代码 return Value.ToString(); 给我一个 CS8603 可能的空引用返回 警告,我的问题是为什么会出现这个警告?

我使用了 where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable 泛型约束来匹配数字类型,Value 实际上是值类型,而不是引用类型。也没有任何值类型的重载 ToString() 方法,默认的 实现 例如对于 Int32 返回的是非空的 string。MSDN 继承者注意事项 也说:

你的 ToString() 重写不应该返回 Empty 或者 null 字符串。

编译器是否抱怨某些类型,这些类型可以满足通用约束并从 ToString() 返回 null

我可以通过使返回类型可空来避免警告。

public override string? ToString()
{
    return Value.ToString();
}

或通过使用 null 合并运算符

public override string ToString()
{
    return Value.ToString() ?? "";
}

或者使用空值忽略运算符
public override string ToString()
{
    return Value.ToString()!;
}

但这些选项看起来大多像是把戏,我正在寻找对此行为的解释,它是出于设计还是其他原因发生的?除了上述方法之外,是否有任何避免警告的方法?
顺便说一句,这个选项不起作用,警告仍然存在。
[return: MaybeNull]
public override string ToString()
{
    return Value.ToString();
}

我正在使用.NET Core 3.1和VS 2019 16.4.2,但我认为这并不是很重要。 提前感谢您的帮助!

1
ToString 方法不应该返回空字符串,应该返回有意义的内容。 - Trevor
1
似乎在Roslyn中缺少一些内容。向下滚动至_UPDATE 2019-10-08 (II)_: https://cezarypiatek.github.io/post/non-nullable-references-in-dotnet-core/ - ZorgoZ
@Çöđěxěŕ 这是真的,我已经按照msdn的指南进行了操作。 - Pavel Anikhouski
@ZorgoZ 感谢您的更新,我没有看到这个更新和PR,实际上它与上面的msdn链接相矛盾。 - Pavel Anikhouski
@canton7,这听起来很合理,但是在这个msdn部分和GitHub PR的功能之间存在一些混淆。 - Pavel Anikhouski
显示剩余3条评论
1个回答

16

object.ToString()的签名如下:

public virtual string? ToString()

也就是说,对象的ToString()方法被定义为返回一个可能为null的字符串。

你对Node.ToString()的重载加强了这个要求,并承诺返回一个非null的字符串。这没问题,例如Int32就是这样做的(正如你所指出的)。

但是,你的Node.ToString()方法返回Value.ToString()的值。我们刚才看到,这个ToString方法(即object.ToString())可能返回null。因此编译器警告你的Node.ToString()方法可能会意外返回null,如果Value.ToString()返回null


这就解释了为什么你发现声明Node.ToString()为:

public override string? ToString()

抑制警告: 现在您声明了您的 Node.ToString() 方法可能返回 null,因此如果 Value.ToString() 返回 null 并且您返回该值,就不会有问题。

这也解释了为什么编写 return Value.ToString() ?? ""; 将抑制警告: 如果 Value.ToString() 返回 null,那么该代码将确保 Node.ToString() 不会返回 null


最好如何解决这个问题?由您决定。

您是否想承诺您的 Node.ToString() 方法永远不会返回 null?如果是这样,您需要确定如果 Value.ToString() 返回 null,您该怎么办。

否则,最好遵循已有的模式,并声明您的 Node.ToString() 方法可能返回 null


为什么 object.ToString() 返回 string??请参见此线程进行全面讨论,但要点是因为存在一些人不遵循不应返回 null 或空字符串的指南,所以在某些情况下 ToString 方法会返回 null

  1. 如果您引用了没有可空注释的类型,则 object.ToString() 返回 string? 意味着除非检查 null,否则会出现警告。这保护您免受编写不良的 ToString 方法的影响。
  2. 如果您引用了带有可空注释的类型,则:
    1. 作者遵循了指南,并声明其 ToString 方法返回 string。在这种情况下,编译器假设您不会得到 null
    2. 作者明确未遵循指南,并声明其 ToString 方法返回 string?。在这种情况下,您需要检查 null

请注意,在 Visual Studio 中创建 ToString 的重载时,生成的方法返回 string(即使被重载的方法返回 string?)。这提示您遵循指南。

唯一的麻烦之处是当您处理泛型类型或已转换为 object 的类型时。在这种情况下,编译器不知道对象的 ToString 方法是否遵循指南。由于 object.ToString 返回 string?,编译器会假设最坏情况。如果愿意,您可以使用空值合并运算符 ! 来覆盖此假设。


1
谢谢您详细的回答,很有道理。我在这里没有考虑到 object.ToString()。但是为什么 [return: MaybeNull] 在这种情况下不起作用呢? - Pavel Anikhouski
1
MaybeNull 只影响方法的 契约 -- 它会影响调用该方法的其他代码,但不会影响方法内部的代码。在这里进行比较 - canton7

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