何时/为什么应该重写ToString方法?

106

我正在学习C#,想知道覆盖ToString的意义和好处,就像下面的示例所示。

是否可以使用常见方法而不是重写来以某种更简单的方式完成此操作?

public string GetToStringItemsHeadings
{
    get { return string.Format("{0,-20} {1, -20}", "Office Email", "Private Email"); }
}


public override string ToString()
{
    string strOut = string.Format("{0,-20} {1, -20}", m_work, m_personal);
    return strOut;
}

4
视情况而定,如果你想在用户界面(UI)中显示对象,那么就需要它;否则通常只是为了调试 - 你会在VS的监视窗口中看到ToString输出。(但你也可以通过类上的属性来实现这一点。)鉴于你已经有了标题和输出,看起来这个程序正在使用它来使用Console.WriteLine(obj.GetToStringItemsHeadings); Console.WriteLine(obj);或类似的方式将对象转储出来。 - Rup
2
我不太理解这个问题。可以用不同的方法来完成吗?是的,但为什么你想要用不同的方法呢? - Konrad Rudolph
5
我建议在属性名中避免使用"GetToString"这个词,这样你可以得到类似于"Console.WriteLine(obj.ItemHeadings)"的结果。请注意,这不会改变原意。 - Svish
将ItemHeadings属性更改为静态,因为它不需要任何"this"。 - eFloh
你可能想查看这个问题:https://dev59.com/y1zUa4cB1Zd3GeqP452L - Yuri Ghensev
17个回答

134
我将直接从.NET Development Series的Framework Design Guidelines中给出答案。 避免ToString中抛出异常。 考虑返回与实例相关的唯一字符串。 考虑使ToString的输出成为此类型上任何解析方法的有效输入。 确保ToString没有可观察的副作用。 仅在要求适当权限后,报告安全敏感信息通过ToString的重写。如果权限要求失败,则返回不包含安全敏感信息的字符串。 Object.ToString方法旨在用于一般显示和调试目的。默认实现仅提供对象类型名称。默认实现不是很有用,建议覆盖该方法。

在返回有趣的人类可读字符串时覆盖ToString。默认实现并不是很有用,自定义实现几乎总是可以提供更多价值。

优先选择友好的名称而不是唯一但不可读的ID。

正如Chris Sells在指南中解释的那样,值得一提的是ToString对于用户界面通常是危险的。一般来说,我的经验法则是公开一个属性,用于将信息绑定到UI,并将ToString重写留给开发人员用于显示诊断信息。您还可以使用DebuggerDisplayAttribute装饰类型。

尝试保持从ToString返回的字符串简短。调试器使用ToString获取对象的文本表示形式以显示给开发人员。如果字符串比调试器可以显示的长度长,则会影响调试体验。

基于当前线程文化进行字符串格式化,以返回与文化相关的信息。

如果从ToString返回的字符串与文化相关或有多种格式化字符串的方式,则应提供重载ToString(string format)或实现IFormattable。例如,DateTime提供了重载并实现了IFormattable

不要ToString返回空字符串或null。

我信奉这些准则,你也应该这样做。我不能告诉你只是因为ToString的这一个准则,我的代码如何得到改进。对于像IEquatable(Of T)IComparable(Of T)这样的东西也是一样的。这些东西使你的代码非常实用,而且你不会后悔花费额外的时间来实现它们。

就个人而言,我从来没有真正用过ToString来进行用户界面设计,我总是暴露某种属性或方法。大多数情况下,您应该将ToString用于调试和开发人员目的。使用它来显示重要的诊断信息。


6
好的,您在询问这些指南吗?链接为:http://msdn.microsoft.com/en-us/library/ms229042.aspx。 - Jodrell
2
@Jodrell 我相信这是从框架设计指南中提取的。 - Joseph Yaduvanshi
吉姆是正确的,这些来自Framework Design Guidelines,这是MSFT用于开发其托管API的准则。我强烈建议购买这本书,因为它将改变您工程软件和生成高质量代码的方式。 - David Anderson
我认为这个是我最喜欢的。不要从ToString返回空字符串或null。 - tiltedtimmy

127
  • 您需要覆盖ToString吗? 不需要。

  • 还有其他方法可以获取您对象的字符串表示形式吗? 是的。

但是,通过使用ToString,您正在使用一种对于所有对象都通用的方法,因此其他类也知道这个方法。例如,每当.NET框架想要将对象转换为字符串表示形式时,ToString是首选(如果您想提供更复杂的格式化选项,则还有其他选项)。

具体而言,

Console.WriteLine(yourObject);

这将调用yourObject.ToString()


13
是的。在MVC或ASP.Net开发中非常有用,因为@yourObject<%=yourObject%>将被转换为"ToString-ed"。 - Ben Lesh
4
在填充下拉框时,默认调用 ToString 方法以获取项文本。 - Davi Fiamenghi
4
小心这样的做法。 真实故事:一位学生重写 ToString 方法时不仅返回一个字符串,还改变了数据。他在调试程序时遇到断点,但数值却不停变化。显然,每次他检查数值时 ToString 方法就会被执行,所以数值即使在程序未运行时也会发生改变。 - JNF
3
“such practice”是什么?ToString不应该实际改变对象的状态。但这不是我所倡导的。 - Konrad Rudolph
10
@JNF 我不接受这个观点。ToString的本意就是要被重写。这是毋庸置疑的。在这个陈述上加上警告并没有什么生产性的作用。如果你做错了,那是你自己的责任。实际上,你的学生违反了David Anderson答案中发布的覆盖ToString的官方指南的第4条建议。 - Konrad Rudolph
显示剩余6条评论

39

覆盖ToString()方法可以使得类有一个有用的人类可读的字符串表示。

这意味着输出可以展现关于你的类的有用信息。例如,如果你有一个Person类,你可能会选择让ToString()输出该人的id、名字、姓氏等信息。这在调试或日志记录时非常有用。

关于你的示例-不知道这个类是什么,很难判断你的重写是否有用-但实现本身是可以的。


13

总的来说,重载ToString()方法是适当的,但需要仔细考虑显示内容的目的。

更好的问题应该是:

为什么要重写ToString()方法?

ToString()方法是查看对象状态的窗口。强调状态是必须的。像Java/C#这样的面向对象的语言通过把所有内容封装在类中来滥用面向对象编程模型。想象一下,如果你使用的是不遵循强OOP模型的语言,你会使用类还是函数。如果你会将其作为函数(即动词、行动)使用,并且内部状态仅在输入/输出之间暂时维护,那么ToString()方法就不会增加价值。

正如其他人提到的,重载ToString()方法时需要考虑显示什么,因为它可能被调试器或其他系统使用。

我喜欢把ToString()方法想象成对象的--help参数。它应该是简短的、可读的、明显的和易于显示的。它应该显示对象是什么而不是做了什么。有了这些考虑,让我们考虑...

用例 - 解析TCP数据包:

不是应用程序级别的网络捕获,而是像pcap捕获这样更具体的东西。

你想为TCP层重载ToString()方法,以便可以将数据打印到控制台。它会包括什么?你可以疯狂地解析所有TCP细节(即TCP很复杂)...

这其中包括:

  • 源端口
  • 目标端口
  • 序列号
  • 确认号码
  • 数据偏移量
  • 标志
  • 窗口偏移量
  • 校验和
  • 紧急指针
  • 选项(我甚至不会去那里)

但如果你在100个数据包上调用TCP.ToString()时,你想接收所有这些垃圾信息吗?当然不,这会造成信息过载。最简单和明显的选择也是最明智的选择...

展示人们期望看到的内容:

  • 源端口
  • 目标端口

我更喜欢一个对人类来说易于解析的合理输出,但您的体验可能会有所不同

TCP:[destination:000, source:000]

没有什么复杂的,输出不是让机器解析的(除非人们正在滥用你的代码),旨在使人更容易阅读。

但是我之前提到的所有其他有用信息怎么样?我会讲到那个问题的,但首先...


ToString()是有史以来最有价值且未被充分利用的方法之一

原因有两个:

  1. 人们不理解 ToString() 的用途
  2. 基类 'Object' 缺少另一个同等重要的字符串方法。

原因1-不要滥用 ToString() 的实用性:

很多人使用 ToString() 来提取对象的简单字符串表示形式。C# 手册甚至声明:

ToString 是 .NET Framework 中的主要格式设置方法。它将对象转换为其字符串表示形式,以便于显示。

显示,而不是进一步处理。这并不意味着,拿走我上面 TCP 包的漂亮字符串表示形式,并使用正则表达式提取源端口:: cringe ::。

正确的做法是直接在 SourcePort 属性上调用 ToString()(顺便说一下,它是 ushort,因此 ToString() 应该已经可用)。

如果您需要更强大的东西来打包复杂对象的状态以供机器解析,那么最好使用结构化序列化策略。

幸运的是,这样的策略非常普遍:

  • ISerializable (C#)
  • Pickle (Python)
  • JSON (Javascript 或任何实现它的语言)
  • SOAP
  • 等...

注意:除非使用 PHP,因为有一个功能可以做到这一点:: snicker ::

原因 2 - ToString() 是不够的:

我还没有看到一种实施该方法的语言,但我已经在野外看到并使用了此方法的变体。

其中一些包括:

  • ToVerboseString()
  • ToString(verbose=true)

基本上,TCP 数据包的那个毛茸茸的状况应该为人类可读性所描述。为了避免“反复敲打”谈论 TCP,我将“指出”一个我认为 ToString() 和 ToVerboseString() 被低估的 #1 案例...

用例-数组:

如果您主要使用一种语言,则可能对该语言的方法感到满意。对于像我这样在不同语言之间跳跃的人来说,各种各样的方法数量可能会很烦人。

即,这已经困扰我的次数大于每个印度教神的所有手指的总和。

各种情况,语言使用通用技巧少数能够正常工作的情况。有些

print(array.ToString());

输出结果: 'Array[x]' 或 'Array[x][y]'

x表示第一维中项目的数量,y表示第二维中项目的数量或者指示第二维是不规则的某个值(如最小/最大范围)。

而且:

print(array.ToVerboseString());

因为我欣赏美丽事物,所以输出整个shebang的漂亮打印版。

希望这能对一个长期困扰我的话题有所启示。至少我在回答中撒了一些PHP大神们下票的“诱饵”。


你写道,“我还没有看到一种语言在核心实现了这个”。在我看来,Python非常接近这一点;例如,打印一个数组可以简洁地显示所有数据。这是我在C#/Java中所缺少的东西,尽管我喜欢它们的严谨性。当你需要比ToString()更进一步时,有像Python列表推导语法这样的东西会很好;我想string.Join()也类似,但不够灵活。 - Jon Coombs
1
@JCoombs 我并不反对,推导式确实很棒。通过推导式在Python中遍历数据结构可以让生活变得更加轻松,但这种方法仍然需要对对象的内部结构有深入的了解。对于从外部库导入的类,这并不总是显而易见的。对于库的作者来说,重写ToString()并提供有用的人类可读表示是一个好的实践,但也很好有一个通用的转储方法,以一般结构化的人类可读格式输出对象的结构(例如JSON)。 - Evan Plaice
非常好的观点。因此,它可能会自动序列化为类似JSON的东西,而不仅仅是显示typeof。 (到目前为止,我只看到过将.NET对象序列化为XML,但我是.NET的新手)但是,危险可能是诱惑将ToString()视为机器可读的? (例如,期望能够反序列化它。) - Jon Coombs
1
@JCoombs 是的,在功能层面上,JSON和XML具有相同的作用。许多人确实使用ToString作为机器可读输出;这是否好还有争议。我最大的问题是——往往情况下——以人类可读格式输出对象的状态是一件麻烦的事情。ToString()实现经常被低估,而在调试器监视列表之外读取对象状态则更加困难。序列化表示形式作为显示对象整个状态的备份是不错的选择,但更好的ToString实现是最佳选择。 - Evan Plaice
如果 ToString() 是用于调试,那么为什么它是从 StringBuilder 中提取整个文本的唯一方法 - 这是一个重要的功能? - Dan W

9
这主要是关于良好的实践,ToString() 通常用于返回对象的字符串表示形式,供人类使用。通常相同的字符串也可以用于重新生成对象(例如 intDateTime),但这并不总是可以做到的(例如一棵树可能具有有用的字符串表示形式,仅显示计数,但显然不能使用它来重建它)。
特别是调试器将使用此功能在监视窗口、立即窗口等中显示变量,因此对于调试来说,ToString 实际上是非常宝贵的。
一般来说,这种类型也经常有一个明确的成员返回一个字符串。例如,一个纬度/经度对可能有 ToDecimalDegrees 返回 "-10,50" 这样的格式,但它也可能有 ToDegreesMinutesSeconds,因为这是另一种表示纬度/经度对的方式。同样的类型也可能通过其中一个来覆盖 ToString,以提供调试的 '默认',甚至潜在地用于呈现 web 页面(例如 Razor 中的 @ 构造将非字符串表达式的 ToString() 结果写到输出流中)。

6

object.ToString() 将一个对象转换为字符串表示形式。如果您想在用户调用类的 ToString() 方法时更改返回的内容,则需要在该类中重写 ToString() 方法。


6

虽然我认为提供的最有用的信息已经足够了,但我要补充一点:

  • ToString() 应该被重写。它的默认实现返回类型名称,虽然有时可能很有用(特别是在处理大量对象时),但在大多数情况下不够用。

  • 记住,为了调试目的,你可以依赖 DebuggerDisplayAttribute。你可以在这里阅读更多信息。

  • 通常情况下,你可以在 POCO 上重写 ToString()。POCO 是数据的结构化表示形式,通常可以转换为字符串。

  • 将 ToString 设计为对象的文本表示形式。可能是它的主要字段和数据,也可能是集合中的项目数量的描述等。

  • 始终尝试将该字符串适应为单行,并仅包含必要的信息。如果你有一个 Person 类,其中包含 Name、Address、Number 等属性,请仅返回主要数据(例如 Name 和某个 ID 号码)。

  • 注意不要覆盖好的 ToString() 实现。一些框架类已经实现了 ToString()。覆盖该默认实现是不好的:人们会期望从 ToString() 得到某个特定的结果,但实际上得到另一个结果。

不要真的害怕使用 ToString()。唯一需要小心的是返回敏感信息。除此之外,风险是很小的。当然,正如一些人指出的那样,其他类在获取信息时将使用你的 ToString。但是,什么时候返回类型名称会被认为比获取一些实际信息更好呢?


5

如果您不覆盖ToString,则将获得基类实现,对于Object来说,它只是类的短类型名称。

如果您想要其他更有意义或有用的实现方式,请覆盖它。


当将您的类型的列表用作ListBox的数据源时,这可能非常有用,因为ToString将自动显示。

另一种情况是当您想要传递您的类型到String.Format时,它调用ToString以获取您的类型的表示。


4

还没有人提到的一点是:通过覆盖 ToString() ,您也可以考虑实现 IFormattable ,以便可以执行以下操作:

 public override ToString() {
   return string.Format("{0,-20} {1, -20}", m_work, m_personal);
 }

 public ToString(string formatter) {
   string formattedEmail = this.ToString();
   switch (formatter.ToLower()) {
     case "w":
       formattedEmail = m_Work;
       break;
     case "p":
       formattedEmail = m_Personal;
       break;
     case "hw":
       formattedEmail = string.Format("mailto:{0}", m_Work);
       break;
   }
   return formattedEmail;
}

这可能很有用。

请问您能否举一个框架类提供覆盖该方法的示例?我所知道与之相关的唯一内容是IFormattible接口。 - tm1
@tm1 任何继承自System.Object的对象都将拥有可重写的ToString()方法,但您是正确的,格式化程序方法不应该是override,而且应该真正参考IFormattible接口以实现完全一致性。 - Zhaph - Ben Duguid
几乎 - IFormattable需要一个IFormatProvider参数。感谢您编辑帖子。 - tm1
确实,它有两种方法,我表达的值是在显示的那个方法中。 - Zhaph - Ben Duguid

3
重写ToString()方法的一个好处是Resharper工具的支持:使用快捷键Alt + Ins -> "格式化成员",它会为你编写ToString()方法代码。

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