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


103

我正在研究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 2012年

2
我不太明白这个问题。可以做不同的事情吗?是。但是,为什么以不同的方式来做。
康拉德·鲁道夫2012年

5
我真的会跳过GetToString属性名称...然后,您会得到Console.WriteLine(obj.ItemHeadings)
Svish

并将ItemHeadings属性更改为静态,因为它不需要任何“ this”。
eFloh 2012年

Answers:


127
  • 您需要覆盖ToString吗?没有。

  • 您能否以其他方式获得对象的字符串表示形式?是。

但是通过使用ToString您使用的是所有对象都通用的方法,因此其他类也知道此方法。例如,每当.NET框架希望将对象转换为字符串表示形式时,ToString它都是主要的候选对象(如果要提供更详尽的格式设置选项,则还有其他选择)。

具体来说,

Console.WriteLine(yourObject);

会调用yourObject.ToString()


13
是。同样,这在MVC或ASP.Net开发中非常有用,在该开发中@yourObject<%=yourObject%>将要进行“ ToString编辑”。
Ben Lesh

4
同样,在填充组合框时,ToString是获取项目文本的默认调用
Davi Fiamenghi 2012年

3
当心这种做法。真实的故事:一个学生用一种除返回字符串外还更改数据的方法覆盖了ToString。他正在调试程序,但是当程序处于断点时,值不断变化。显然-每次他检查ToString的值都被执行时-即使程序未运行,该值也会更改。
JNF 2012年

3
@JNF什么是“这种做法”?ToString实际上不应该更改对象的状态。但这不是我提倡的。
康拉德·鲁道夫2012年

8
@JNF我不接受。ToString被覆盖。期。在此声明上加注是无济于事的。如果做错了,那是你自己的错。实际上,您的学生违反ToString了大卫·安德森(David Anderson)答案中发布的正式的“超越准则”清单中的第4点。
康拉德·鲁道夫2012年

129

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

避免引发异常ToString

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

ToString对于这种类型的任何解析方法,请考虑将输出作为有效输入。

确保ToString没有明显的副作用。

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

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

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

DO喜欢一个友好的名称,在特有的,但不能读取ID。

值得一提的是,克里斯·塞尔斯(Chris Sells)在准则中也解释了ToString通常对用户界面很危险的内容。通常,我的经验法则是公开一个将用于将信息绑定到UI的属性,而将ToString替代信息显示给开发人员。您也可以装饰您的类型DebuggerDisplayAttribute

请勿尝试使字符串从ToString短返回。调试器用来ToString获取要显示给开发人员的对象的文本表示形式。如果字符串长于调试器可以显示的长度,则将妨碍调试体验。

DO返回字符串文化相关的信息格式化根据当前线程的文化。

DO提供过载ToString(string format),或实现IFormattable,如果从字符串返回ToString是文化敏感或有各种方式来格式化字符串。例如,DateTime提供重载和实现IFormattable

不要从中返回空字符串或nullToString

我发誓要遵守这些准则,您应该这样做。仅凭此一项针对的指南,我无法告诉您我的代码如何得到改进ToString。类似的事情也适用于IEquatable(Of T)IComparable(Of T)。这些东西使您的代码具有很好的功能,并且您不会后悔花额外的时间来实现任何代码。

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


6
很好的答案,这些是msdn.microsoft.com/zh-cn/library/ms229042.aspx所参考的指南吗?
Jodrell 2012年

2
@Jodrell我相信它取自“ 框架设计指南”
Jim Schubert

6
需要更好的引用。
Ben Voigt 2012年

13
我不知道SO会变成维基百科。
David Anderson

14
并非如此,但是当您引用准则时,提供来源是不可否认的优点。
Falanwe 2012年

39

通过覆盖ToString(),您可以提供有用的,人类可读的类的字符串表示形式。

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

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


13

总是合适的,但请仔细考虑所显示内容的意图

更好的问题是:

为什么要覆盖ToString()?

ToString()是进入对象状态的窗口。强调国家作为要求。像Java / C#这样的OOP语言强烈地通过将所有内容封装在一个类中来滥用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数据包的漂亮字符串表示形式,并使用regex :: cringe ::拉出源端口。

正确的做事方式是,调用toString()直接在源端口属性(BTW是一个USHORT这样的ToString()应该已经可用)。

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

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

  • 可序列化(C#)
  • 泡菜(Python)
  • JSON(JavaScript或实现它的任何语言)
  • 肥皂
  • 等等...

注意:除非您使用PHP,否则会因为:: snicker ::而导致使用herp-derp:

原因2-ToString()不够:

我还没有看到一种语言可以在核心实现它,但是我已经看到并使用了这种方法的各种变体。

其中一些包括:

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

基本上,应该描述TCP数据包状态的混乱状况以便于人类阅读。为了避免在讨论TCP时“打败一匹死马”,我将“指责”我认为ToString()和ToVerboseString()使用不足的#1情况。

用例-数组:

如果您主要使用一种语言,那么您可能会对这种语言的方法感到满意。对于像我这样在不同语言之间跳来跳去的人,各种各样的方法可能会令人讨厌。

就是说,这使我烦恼的次数大于每个印度教神的手指之和的总和。

各种地方语言使用常见的情况的黑客一些得到的权利。有些需要重新发明轮子,有些需要进行浅层倾卸,有些需要进行深层倾卸,但都没有按照我希望他们的方式工作...

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

print(array.ToString());

输出:“ Array [x]”或“ Array [x] [y]”

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

和:

print(array.ToVerboseString());

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

希望这可以阐明一个困扰我很长时间的话题。至少我为PHPers撒了一些巨魔诱饵来否决这个答案。


您写道:“我还没有看到一种可实现此目的的语言”。在我看来,Python非常接近于此。例如,打印数组会简洁地给出所有数据。尽管我确实喜欢它们的严格性,但这是我在C#/ Java中缺少的一件事。而且,当您需要超越ToString()时,最好使用Python的列表理解语法。我猜想string.Join()相似,但灵活性较差。
乔恩·库姆斯

1
@JCoombs我不同意,理解力很棒。通过理解在Python中遍历数据结构使工作变得更加轻松,但是这种方法仍然需要对对象的内部结构有深入的了解。从外部库导入的类并不总是很明显。对于库作者来说,重写ToString()并提供有用的人类可读表示是一个很好的做法,但是最好有一种通用的转储方法,以一种通用的结构化人类可读格式(例如JSON)输出对象的结构。
Evan Plaice

优点。因此,除了显示基本的typeof之外,它可能会自动序列化为JSON之类的内容。(到目前为止,我只看到过.NET对象序列化为XML,但是我是.NET的新手)但是,那么危险就可能是把ToString()视为机器可读的诱惑了吗?(例如希望能够反序列化。)
乔恩·库姆斯

1
@JCoombs是的,在功能级别上,JSON和XML具有相同的目的。很多人的确将ToString用作机器可读的输出。这是否不好还值得商.。我最大的困难是-常常是-以人类可读的格式输出对象的状态是很痛苦的。ToString()实现通常未得到充分利用,并且在调试器监视列表之外读取对象的状态是很痛苦的。序列化表示形式可以很好地用作显示对象整个状态的备份,但是更好的ToString实现是最好的选择。
Evan Plaice 2013年

如果ToString()是为了调试,那么为什么它是从StringBuilder中提取整个文本的唯一方法-一个重要的功能?
Dan W

9

实际上,这与良好实践有关。

ToString()在许多地方使用来返回对象的字符串表示形式,通常供人类食用。通常,可以使用相同的字符串为对象补水(例如int或想想DateTime),但这并不总是给定的(例如,一棵树可能具有有用的字符串表示形式,该字符串表示形式仅显示一个Count,但显然您不能使用来重建它)。

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

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


6

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


6

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

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

  • 请记住,出于调试目的,您可以依赖DebuggerDisplayAttribute。您可以在此处了解更多信息。

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

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

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

  • 注意不要忽略的良好实现ToString()。一些框架类已经实现ToString()。覆盖默认实现是一件坏事:人们会期望从中ToString()获得某种结果并获得另一种结果。

不必担心使用ToString()。我唯一要注意的是返回敏感信息。除此之外,风险很小。当然,正如某些人指出的那样,其他类在获取信息时都会使用ToString。但是,什么时候返回类型名称会比获得一些实际信息更好呢?


5

如果不重写,ToString则将获得基类实现,这Object只是该类的简称。

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


当使用您的类型的列表作为的数据源时,这将很有用,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,19年

@ tm1任何继承自该对象的对象System.Object都将具有可重写的ToString()方法,但是您是正确的,格式化程序方法不应该override并且应该真正引用IFormattible接口以实现完全一致性。
扎夫-本·杜吉德

几乎- IFormattableIFormatProvider参数。不过,感谢您编辑您的帖子。
tm1,19年

确实,它有两种方法,我表示的值是所示的方法;)
Zhaph-Ben Duguid,


2

在某些情况下,它使在调试器监视窗口中读取自定义类的值更加容易。如果我确切地知道要在监视窗口中看到的内容,那么当我用该信息覆盖ToString时,就会看到它。


1

定义结构(有效地是用户基元)时,我发现具有match ToStringParseTryParse方法是一个好习惯,特别是对于XML序列化。在这种情况下,您将把整个状态转换为字符串,以便以后可以读取。

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

同样,正如Robbie所说的那样,重写ToString允许您调用ToString与type一样基本的引用object


1

当您有一个对象而不是字符串表示的直观含义时,可以使用它,例如person。因此,例如,如果您需要打印此人,则可以使用此替代来准备其格式。


1

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

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

IFormattable当您要返回用户友好的本地化文本时,请实现该接口。此接口使用参数format和定义ToString重载formatProvider。formatProvider可帮助您以一种文化意识的方式来格式化文本。

另请参见:Object.ToString和IFormattable


0

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

但是,您似乎在重写ToString方法,而不是返回属性的常规字符串数据,而是执行标准的格式化模式。由于您使用string.format和padding。

因为您说的是学习,所以练习似乎也触及了与封装和代码重用有关的面向对象编程的核心原理。

使用您为填充设置的参数的string.format确保每次调用该属性的代码都以相同的方式格式化该属性。同样,继续前进,您只需要在一个地方而不是在多个地方进行更改即可。

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


0

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

但是,与之前所说的相一致,是要以人类可读的方式表示所讨论的对象。


0

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.