105

我正在学习 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

17 回答 17

130

我将直接从.NET 开发系列 的框架设计指南中为您提供答案。

避免抛出异常ToString

考虑返回与实例关联的唯一字符串。

考虑将输出作为ToString此类型的任何解析方法的有效输入。

确保ToString没有可观察到的副作用。

ToString仅在要求适当的许可后才通过覆盖报告安全敏感信息。如果权限请求失败,则返回一个不含安全敏感信息的字符串。

Object.ToString方法旨在用于一般显示和调试目的。默认实现只提供对象类型名称。默认实现不是很有用,建议重写该方法。

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

与唯一但不可读的 ID 相比,更喜欢友好的名称。

值得一提的是,Chris Sells 在指南中也解释了这ToString通常对用户界面很危险。一般来说,我的经验法则是公开一个用于将信息绑定到 UI 的属性,并保留ToString用于向开发人员显示诊断信息的覆盖。你也可以用它来装饰你的DebuggerDisplayAttribute字体。

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

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

如果返回的字符串是文化敏感的,或者有多种格式化字符串的方法,提供重载ToString(string format)或实现。例如,提供重载和实现。IFormattableToStringDateTimeIFormattable

不要ToString

我发誓这些准则,你应该这样做。我无法告诉你我的代码是如何仅通过这一准则来改进的ToString。同样的事情也适用于和之类的IEquatable(Of T)东西IComparable(Of T)。这些东西使您的代码非常实用,您不会后悔花额外的时间来实现它。

就个人而言,我从来没有真正使用ToString 用户界面,我总是公开某种属性或方法。大多数时间你应该ToString用于调试和开发目的。用它来显示重要的诊断信息。

于 2012-04-23T10:02:07.353 回答
127
  • 你需要覆盖ToString吗?不。

  • 你能用另一种方式得到你的对象的字符串表示吗?是的。

但是通过使用ToString,您正在使用所有对象通用的方法,因此其他类知道此方法。例如,每当 .NET 框架想要将对象转换为字符串表示时,ToString它就是一个主要的候选者(如果您想提供更精细的格式化选项,还有其他的)。

具体来说,

Console.WriteLine(yourObject);

会调用yourObject.ToString().

于 2012-04-23T09:40:11.173 回答
39

覆盖ToString()允许您为类提供有用的人类可读的字符串表示。

这意味着输出可以揭示有关您的班级的有用信息。例如,如果您有一个 Person 类,您可能会选择ToString()输出人员的 id、他们的名字、他们的姓氏等。这在调试或记录时非常有用。

关于您的示例-在不知道此类是什么的情况下很难判断您的覆盖是否有用-但实现本身还可以。

于 2012-04-23T09:38:51.587 回答
13

它总是合适的,但要仔细考虑你所展示的内容背后的意图

一个更好的问题是问:

为什么要覆盖 ToString()?

ToString() 是进入对象状态的窗口。强调状态为要求。强烈的 OOP 语言(如 Java/C#)通过将所有内容封装在一个类中来滥用 OOP 模型。想象一下,您正在使用一种不遵循强大的 OOP 模型的语言进行编码;考虑您是使用类还是函数。如果您将其用作函数(即动词、动作)并且内部状态仅在输入/输出之间临时维护,则 ToString() 不会增加价值。

就像其他人提到的那样,考虑使用 ToString() 输出的内容很重要,因为调试器或其他系统可以使用它。

我喜欢把 ToString 方法想象成一个对象的 --help 参数。它应该简短、易读、明显且易于显示。它应该显示对象什么而不是它的作用。考虑到所有这些,让我们考虑...

用例 - 解析 TCP 数据包:

不是仅应用程序级别的网络捕获,而是具有更多内容的东西,例如 pcap 捕获。

您只想为 TCP 层重载 ToString(),以便将数据打印到控制台。它会包括什么?您可能会发疯并解析所有 TCP 详细信息(即 TCP 很复杂)...

其中包括:

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

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

公开人们期望看到的内容:

  • 源端口
  • 目的端口

我更喜欢一个易于人类解析的合理输出,但YMMV

TCP:[destination:000, source:000]

没什么复杂的,输出不是供机器解析的(即除非人们滥用您的代码),其预期目的是为了人类可读性。

但是我之前谈到的所有其他有趣的信息呢,不是也很有用吗?我会解决的,但首先...


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

有两个原因:

  1. 人们不明白 ToString() 的用途
  2. 基类“对象”缺少另一个同样重要的字符串方法。

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

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

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

展示,不做进一步处理。这并不意味着,使用我上面对 TCP 数据包的漂亮字符串表示,并使用正则表达式 ::cringe:: 拉取源端口。

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

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

幸运的是,这样的策略很常见:

  • 可序列化 (C#)
  • 泡菜(蟒蛇)
  • JSON(Javascript 或任何实现它的语言)
  • 肥皂
  • ETC...

注意:除非您使用 PHP,因为 herp-derp 有一个函数用于该 ::snicker::

原因 2 - ToString() 还不够:

我还没有看到一种在核心实现这一点的语言,但我已经在野外看到并使用了这种方法的变体。

其中一些包括:

  • ToVerboseString()
  • ToString(详细=真)

基本上,应该描述 TCP 数据包状态的毛茸茸的混乱,以提高人类可读性。为了避免在谈论 TCP 时“打死马”,我将“指责”我认为 ToString() 和 ToVerboseString() 未得到充分利用的 #1 案例……

用例 - 数组:

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

也就是说,这让我感到恼火的次数超过了每个印度教神所有手指的总和。

各种情况下,语言使用常见hack一些正确方法。有些需要重新发明轮子,有些需要浅转储,有些需要深转储,它们都没有按照我希望的方式工作......

我要的是一个非常简单的方法:

print(array.ToString());

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

其中 x 是第一个维度中的项目数,y 是第二个维度中的项目数,或者表示第二个维度是锯齿状的某个值(可能是最小/最大范围?)。

和:

print(array.ToVerboseString());

以漂亮的印刷品输出整个she-bang,因为我欣赏漂亮的东西。

希望这能对困扰我很久的话题有所启发。至少我为 PHPers 撒了一点巨魔诱饵来否决这个答案。

于 2012-04-27T09:51:39.230 回答
9

真的,这和任何事情一样都是关于良好实践的。

ToString()在许多地方用于返回对象的字符串表示形式,通常供人类使用。通常可以使用相同的字符串来重新水化对象(例如考虑intDateTime),但这并不总是给定的(例如,树可能有一个有用的字符串表示,它只显示一个计数,但显然你不能使用重建它)。

特别是调试器将使用它在监视窗口、即时窗口等中显示变量,因此ToString对于调试实际上是无价的。

通常,这种类型通常也会有一个显式成员,该成员也返回一个字符串。例如,Lat/Long 对可能有ToDecimalDegreeswhich 返回"-10, 50",但它也可能有ToDegreesMinutesSeconds,因为这是 Lat/Long 对的另一种格式。然后,相同的类型也可能会被ToString其中一个覆盖,以为调试之类的事情提供一个“默认值”,甚至可能为渲染网页之类的事情提供一个“默认值”(例如,@Razor 中的构造将ToString()非字符串表达式的结果写入输出流)。

于 2012-04-23T09:42:27.620 回答
6

object.ToString()将对象转换为其字符串表示形式。如果您想更改用户调用ToString()您创建的类时返回的内容,那么您需要ToString()在该类中覆盖。

于 2012-04-23T09:40:22.477 回答
6

虽然我认为已经提供了最有用的信息,但我将添加我的两分钱:

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

  • 请记住,出于调试目的,您可以依赖DebuggerDisplayAttribute. 你可以在这里阅读更多关于它的信息。

  • 通常,在 POCO 上,您始终可以覆盖ToString(). POCO 是数据的结构化表示,通常可以成为字符串。

  • 将 ToString 设计为对象的文本表示。也许是它的主要字段和数据,也许是对集合中有多少项目的描述,等等。

  • 始终尝试将该字符串放入一行中,并且只包含基本信息。如果您有一个Person具有名称、地址、编号等属性的类,则只返回主要数据(命名一些 ID 号)。

  • 小心不要覆盖ToString(). 一些框架类已经实现了ToString(). 覆盖该默认实现是一件坏事:人们会期望得到某个结果ToString()并得到另一个结果。

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

于 2012-04-25T22:13:04.573 回答
5

如果您不覆盖ToString,那么您将获得基类实现,因为Object它只是类的短类型名称。

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


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

当您想要将您的类型传递给String.Format调用ToString以获取您的类型的表示时,会发生另一种情况。

于 2012-04-23T09:39:42.270 回答
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;
}

这可能很有用。

于 2012-05-04T22:24:49.053 回答
3

覆盖 ToString() 的一个好处是 Resharper 的工具支持:Alt + Ins -> "Formatting members",它会为您编写 ToString()。

于 2012-04-24T21:54:16.853 回答
2

在某些情况下,它可以更轻松地在调试器监视窗口中读取自定义类的值。如果我确切地知道我想在监视窗口中看到什么,当我用该信息覆盖 ToString 时,我就会看到它。

于 2012-04-23T15:59:06.233 回答
1

在定义结构(实际上是用户基元)时,我发现使用 matchToStringParseandTryParse方法是一种很好的做法,尤其是对于 XML 序列化。在这种情况下,您会将整个状态转换为字符串,以便稍后读取。

然而,类是更复杂的结构,通常对于使用ToString和来说过于复杂Parse。他们的ToString方法,而不是保存整个状态,可以是一个简单的描述,可以帮助您识别他们的状态,例如名称或 ID 等唯一标识符,或者可能是列表的数量。

此外,正如 Robbie 所说,覆盖ToString允许您调用ToString与 type 一样基本的引用object

于 2012-04-23T15:04:20.947 回答
1

当你有一个字符串表示不直观的对象时,你可以使用它,比如人。因此,如果您需要例如打印此人,您可以使用此覆盖来准备其格式。

于 2012-04-25T07:19:42.703 回答
1

Object.ToString方法应仅用于调试目的。默认实现显示不是很有用的对象类型名称。考虑重写此方法,以便为诊断和调试提供更好的信息。请考虑日志基础设施通常也使用 ToString 方法,因此您会在日志文件中找到这些文本片段。

不要在方法中返回本地化的文本资源Object.ToString。原因是 ToString 方法应始终返回开发人员可以理解的内容。开发人员可能不会说应用程序支持的所有语言。

IFormattable当您想要返回用户友好的本地化文本时实现接口。此接口使用参数format和定义 ToString 重载formatProvider。formatProvider 可帮助您以文化感知的方式格式化文本。

另请参阅:Object.ToString 和 IFormattable

于 2015-10-03T08:50:48.980 回答
0

更简单取决于您的财产将如何使用。如果您只需要一次格式化字符串,那么覆盖它并没有多大意义。

但是,您似乎正在重写 ToString 方法,以不为您的 property 返回正常的字符串数据,而是执行标准的格式化模式。由于您使用的是带有填充的 string.format 。

因为您说您正在学习,所以该练习似乎还触及了与封装和代码重用相关的面向对象编程的核心原则。

采用您为填充设置的参数的 string.format 可确保每次调用该属性的任何代码都以相同的方式格式化该属性。同样,今后您只需在一处更改而不是多处更改。

很好的问题,也有一些很好的答案!

于 2012-04-25T14:49:53.950 回答
0

我发现覆盖实体类上的 ToString 方法很有用,因为它有助于快速识别测试中的问题,尤其是当断言失败时,测试控制台将调用对象上的 ToString 方法。

但与之前所说的一致,它是为所讨论的对象提供人类可读的表示。

于 2012-04-27T10:05:27.637 回答
0

我对其他答案中提到的框架指南感到满意。但是,我想强调显示和调试目的。

请注意您ToString在代码中的使用方式。您的代码不应依赖对象的字符串表示形式。如果是这样,您绝对应该提供相应的Parse方法。

由于ToString可以在任何地方使用,因此如果您想在以后更改对象的字符串表示形式,这可能是一个可维护性的痛点。在这种情况下,您不能只检查调用层次结构来研究某些代码是否会中断。

于 2019-05-31T07:52:10.663 回答