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

Nullable references types and ToString() overload

请注意,此问题是关于最新的 C# 8 nullable-references,我已通过以下 <Nullable>enable</Nullable> 声明在 csproj 文件中启用它。

考虑以下简单代码

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 Possible null reference return 警告和我的问题实际上为什么它在这里?

我正在使用 where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable 泛型约束来匹配数字类型,Value 实际上是值类型,而不是引用类型。也没有 ToString() 对任何值类型的重载,默认的 implementation for Int32 (for example) returns non-nullable string. MSDN notes to inheritors 也表示

Your ToString() override should not return Empty or a null string.

编译器是否抱怨某些类型可以满足泛型约束和来自 ToString() 的 return null

我可以通过使 return 类型为 nullable

来避免警告
public override string? ToString()
{
    return Value.ToString();
}

或使用空合并运算符

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

或通过 null-forgiving 运算符

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,但我认为这在这里并不重要。 在此先感谢您的帮助!

object.ToString() 的签名是:

public virtual string? ToString()

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

您对 Node.ToString() 的重载收紧了此要求并承诺 return 一个非空字符串。这可以。例如,Int32 会这样做(如您所述)。

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


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

public override string? ToString()

抑制了警告:您现在声明您的 Node.ToString() 方法可能 return null,因此 Value.ToString() return 不是问题s null 然后你 return 这个值。

它还解释了为什么写 return Value.ToString() ?? ""; 会抑制警告:如果 Value.ToString() returned null,该代码将确保 Node.ToString() 没有 return null.


如何最好地解决这个问题?你来决定。

想要保证您的Node.ToString()方法永远不会returnnull吗?如果是这样,您需要弄清楚如果 Value.ToString() returns null.

该怎么办

否则,最好遵循既定模式,并说您的 Node.ToString() 方法可能 return null.


为什么object.ToString()returnstring??有关完整讨论,请参阅 this thread,但要点是在野外有 ToString 方法 do return null,因为有些人不遵循您永远不应该 return null 或空字符串的准则。

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

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

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

来覆盖此假设