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

Posted

技术标签:

【中文标题】可空引用类型和 ToString() 重载【英文标题】:Nullable references types and ToString() overload 【发布时间】:2020-04-22 03:55:42 【问题描述】:

请注意,这个问题是关于最新的 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 可能的空引用返回 警告,我的问题实际上为什么会在这里?

我使用where T : struct, IComparable, IComparable&lt;T&gt;, IConvertible, IEquatable&lt;T&gt;, IFormattable 泛型约束来匹配数字类型,Value 实际上是值类型,而不是引用类型。 ToString() 也不会重载到任何值类型,Int32 的默认 implementation(例如)返回不可为空的 string。 MSDN notes to inheritors 也是这么说的

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

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

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

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

【问题讨论】:

ToString 不应该返回空字符串,返回有意义的东西。 Roslyn 中似乎缺少位。向下滚动到 UPDATE 2019-10-08 (II):cezarypiatek.github.io/post/… @Çöđěxěŕ 是的,我已经按照 msdn 的指南进行了操作。 @ZorgoZ 感谢更新,没看到这个更新和PR,其实和上面的msdn链接矛盾 我记得看到过关于object.ToString() 是否应该返回string?string 的讨论(虽然我现在找不到)。结论是,尽管指导原则是永远不要返回 null,但实际上存在很多可以返回 null 的代码(包括返回对其成员之一调用 ToString() 的结果的代码)。因此,让object.ToString() 返回string 会在大多数人的代码中引入很多警告。为了减少噪音,他们决定记录实际发生的情况,但如果您愿意,可以返回 string 【参考方案1】:

object.ToString() 的签名是:

public virtual string? ToString()

即对象的ToString()方法被定义为返回一个可能为空的字符串。

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

然而,您的Node.ToString() 方法返回来自Value.ToString() 的值。我们刚刚看到这个ToString 方法(即object.ToString())可能会返回null。因此编译器会警告您,如果Value.ToString() 返回null,您的Node.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??请参阅this thread 以获得完整的讨论,但要点是有ToString 方法在野外确实返回null,因为有些人不遵循你不应该遵循的指导方针返回null 或空字符串。

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

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

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

【讨论】:

谢谢你的详细回答,有道理,我这里没有考虑object.ToString()。但是为什么[return: MaybeNull] 在这种情况下不起作用? @PavelAnikhouski MaybeNull 只影响方法的contract -- 它影响调用该方法的其他代码,但不影响方法内部的代码。 Compare here

以上是关于可空引用类型和 ToString() 重载的主要内容,如果未能解决你的问题,请参考以下文章

将可空引用类型转换为不可空引用类型,不那么冗长

可空类型产生警告,因为项目已启用可空引用类型

可空引用类型 - 通过接受的参数返回类型可空性

使用可空引用类型时,OData 元数据不生成可空方面

C# 8中的可空引用类型

可空类型是引用类型吗?