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

Pav*_*ski 4 c# .net-core c#-8.0 nullable-reference-types

请注意,这个问题是关于最新的 C# 8 nullable-references,我已经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
}
Run Code Online (Sandbox Code Playgroud)

该行return Value.ToString();给了我一个CS8603 可能的空引用返回警告,我的问题实际上为什么在这里?

我使用where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable通用约束来匹配数字类型,Value实际上是值类型,而不是引用类型。也没有过载ToString()到任何类型的值,则默认的实现Int32(例如)返回不可为空string。MSDN给继承者的注释也说

您的ToString()覆盖不应返回Emptynull字符串。

编译器是否抱怨某种类型,它可以满足泛型约束并null从返回ToString()

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

public override string? ToString()
{
    return Value.ToString();
}
Run Code Online (Sandbox Code Playgroud)

或使用空合并运算符

public override string ToString()
{
    return Value.ToString() ?? "";
}
Run Code Online (Sandbox Code Playgroud)

或通过空值原谅运算符

public override string ToString()
{
    return Value.ToString()!;
}
Run Code Online (Sandbox Code Playgroud)

但这些选项看起来大多是一种技巧,我正在寻找这种行为的解释,为什么会出现,设计或任何其他原因发生?除了上面的那些,还有什么方法可以避免这个警告?

顺便说一句,这个选项不起作用,警告仍然存在

[return: MaybeNull]
public override string ToString()
{
    return Value.ToString();
}
Run Code Online (Sandbox Code Playgroud)

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

can*_*on7 14

签名为object.ToString()

public virtual string? ToString()
Run Code Online (Sandbox Code Playgroud)

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

你的重载Node.ToString()收紧了这个要求并承诺返回一个非空字符串。这可以。Int32例如,这样做(如您所言)。

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


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

public override string? ToString()
Run Code Online (Sandbox Code Playgroud)

抑制警告:您现在声明您的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??有关完整讨论,请参阅此线程,但要点是有些ToString方法确实会return null,因为有些人不遵循永远不应该 returnnull或空字符串的准则。

  1. 如果您引用的类型是在没有可空注释的情况下构建的,则object.ToString()返回的事实string?意味着您将收到警告,除非您检查null. 这可以保护您免受编写不当的ToString方法的影响。
  2. 如果您引用了使用可为空注释构建的类型,则:
    1. 作者遵循了指导方针,并宣布他们的ToString方法为返回string。在这种情况下,编译器假定您不会得到null.
    2. 作者明确没有遵循指南,并声明他们的ToString方法为返回string?。在这种情况下,您被迫检查null.

请注意,当您ToString在 Visual Studio 中创建 的重载时,生成的方法将返回string(即使重载的方法返回string?)。这会提示您遵循这些准则。

这里唯一的烦恼是当您处理泛型类型或已强制转换为object. 在这种情况下,编译器不知道对象的ToString方法是否遵循准则。因为object.ToStringreturn string?,编译器会假设最坏的情况。如果您希望使用 null-forgiving operator ,您可以覆盖此假设!