为什么object.ToString()存在?

拥有一个IStringable接口不是更优雅和整洁吗?

谁需要这个Type.FullName对象返回给我们?

编辑:每个人都在问为什么我认为它更优雅..

好吧,它就是这样,而不是IComparable,对象将具有CompareTo方法,默认情况下抛出exception或返回0。

有些对象不能也不应该被描述为字符串。 对象可以同样返回string.EmptyType.FullName只是一个任意选择..

对于Console.Write(对象)之类的方法,我认为应该是:Write(IStringable)。

但是,如果你将WriteLine用于除字符串之外的任何东西(或其ToString很明显的东西,如数字),在我看来它只适用于调试模式..

顺便说一句 – 我该怎么评论你们所有人? 我发布答案可以吗?

恕我直言应该有三种虚拟方法从未添加到System.Object …

  • 的ToString()
  • GetHashCode的()
  • 等于()

所有这些都可以按照您建议的界面实现。 如果他们这样做了,我想我们会好得多。 那么为什么这些问题呢? 我们只关注ToString():

  1. 如果ToString()应该由使用ToString()的人实现并显示结果,那么您将拥有编译器无法强制执行的隐式契约。 您假设ToString()被重载,但没有办法强制这样做。
  2. 使用IStringable,您只需要将其添加到generics类型约束或从中派生接口,以要求它在实现对象时使用。
  3. 如果您在重载ToString()时发现的好处是针对调试器,则应该开始使用[System.Diagnostics.DebuggerDisplayAttribute]。
  4. 至于需要通过String.Format()和/或Console.WriteLine将对象转换为字符串的实现,它们可能已经延迟到System.Convert.ToString(对象)并检查了类似’IStringable’之类的东西,故障转移到如果没有实现,类型的名称。
  5. 正如Christopher Estep所指出的那样,它具有特定的文化特征。

所以我想我一个人站在这里说我讨厌System.Object及其所有虚拟方法。 但我确实喜欢C#作为一个整体,我认为设计师做得很好。

注意:如果您打算依赖于ToString()的重载行为,我建议您继续定义您的IStringable接口。 不幸的是,如果你真的想要它,你将不得不为该方法选择另一个名称。

更多

我的同事和我刚刚谈到这个话题。 我认为ToString()的另一个大问题是回答“它用于什么?”的问题。 是显示文字吗? 序列化文本? 调试文字? 全姓名?

让Object.ToString使得像Console.WriteLine这样的API成为可能。

从设计的角度来看,BCL的设计者认为提供实例的字符串表示的能力应该对所有对象都是通用的。 真正的完整类型名称并不总是有用,但他们觉得在根级别具有可自定义表示的能力超过了在输出中看到完整类型名称的轻微烦恼。

如果没有Object.ToString,您可以实现Console.WriteLine,而是执行接口检查,如果接口不存在,则默认为该类型的全名。 但是,每个想要捕获对象实例的字符串表示的API都必须实现此逻辑。 鉴于Object.ToString仅在核心BCL中使用的次数,这将导致大量重复。

我想它存在是因为它对所有物体来说都是非常方便的东西,并且不需要使用add’l cruft。 为什么你认为IStringable会更优雅?

一点也不。

它不需要实现它返回特定于文化的结果。

此方法返回一个对文化敏感的人类可读字符串。 例如,对于值为零的Double类的实例,Double .. ::。ToString的实现可能返回“0.00”或“0,00”,具体取决于当前的UI文化。

此外,虽然它有自己的实现,但它可以被覆盖,并且经常是。

为什么要把它变得更复杂? 它现在的方式基本上确定了每个对象都能够将其值打印到字符串,我看不出有什么问题。

嗯,所以可以在派生类中重写它?

“可串行”表示在很多场景中都很有用,库设计者可能认为ToString()更直接。

使用IStringable,您将不得不进行额外的检查/强制转换,以查看是否可以以字符串格式输出对象。 对于这样一个普通的操作而言,这对于性能的影响太大了,无论如何对99.99%的所有对象都应该是一件好事。

Structs和Objects都有ToString()成员以简化调试。

最简单的例子可以在Console.WriteLine中看到,它接收包括object在内的整个类型列表,但也接收params object[] args 。 由于Console通常是TextWriter的一个层,因此这些语句在写入文件和其他流(套接字)时也很有用(有时)。

它还说明了一个简单的面向对象设计,它表明您不应仅仅因为可以创建接口。

我的新基类:

 class Object : global::System.Object { [Obsolete("Do not use ToString()", true)] public sealed override string ToString() { return base.ToString(); } [Obsolete("Do not use Equals(object)", true)] public sealed override bool Equals(object obj) { return base.Equals(this, obj); } [Obsolete("Do not use GetHashCode()", true)] public sealed override int GetHashCode() { return base.GetHashCode(); } } 

确实没有使用Type.FullName返回给你,但如果返回一个空字符串或null则更少使用它。 你问为什么它存在。 这不是太容易回答,多年来一直是一个备受争议的问题。 十多年前,几种新语言决定在需要时将对象隐式地转换为字符串会很方便,这些语言包括Perl,PHP和JavaScript,但它们都没有完全遵循面向对象的范例。

途径

面向对象语言的设计者有一个更难的问题。 通常,有三种方法可以获取对象的字符串表示forms:

  • 使用多重inheritance,只需从Stringinheritance,您就可以转换为字符串
  • 单inheritance:将ToString作为虚方法添加到基类
  • 要么:使转换操作符或复制构造函数对字符串可重载

也许你会问自己为什么你需要一个ToString或者等同物。 首先? 正如其他一些人已经注意到的那样: ToString是内省所必需的(当你将鼠标hover在任何一个对象实例上时会调用它),调试器也会显示它。 作为程序员,您知道在任何非null对象上,您都可以安全地调用ToString 。 无需演员表,无需转换。

始终在您自己的对象中使用可持久属性中的有意义值实现ToString 是一种很好的编程习惯 如果您需要不同类型的类表示,则重载可以提供帮助。

更多的历史

如果你在历史中深入了解,我们会看到SmallTalk采取更广泛的方法。 基础对象有更多方法,包括printStringprintOn等。

十年后,当Bertrand Meyer撰写他的具有里程碑意义的着作“面向对象软件”时,他建议使用相当广泛的基类GENERAL 。 它包括printprint_linetagged_out ,后者显示对象的所有属性,但没有默认的ToString 。 但他建议“所有用户定义的对象派生的第二个基础对象可以扩展” ,这看起来像我们现在从JavaScript中知道的原型方法。

在C ++中,唯一仍在广泛使用的多inheritance语言,并不存在所有类的共同祖先。 这可能是使用您自己的方法的最佳候选语言,即使用IStringable 。 但是C ++还有其他方法:你可以重载强制转换操作符和复制构造函数来实现可串行性。 在实践中,必须明确关于字符串实现(正如您对IStringable建议)变得非常麻烦。 C ++程序员知道这一点。

在Java中,我们找到了主流语言的第一个toString外观。 遗憾的是,Java有两种主要类型:对象和值类型。 值类型没有toString方法,而是需要使用Integer.toString或强制转换为对象。 事实certificate,这些年来非常繁琐,但Java程序员(包括我)学会了使用它。

然后来了C#(我跳过几种语言,不想让它太长),这是最初用作.NET平台的显示语言,但在最初的怀疑之后certificate非常受欢迎。 C#设计师(Anders Hejlsberg等人)主要研究C ++和Java,并试图充分利用这两个领域。 值类型仍然存在,但引入了拳击。 这使得可以隐式地从Object派生值类型。 添加类似于Java的ToString只是一小步,旨在简化从Java世界的过渡,但现在已经显示出其宝贵的价值。

怪人

虽然你没有直接询问它,但为什么以下必须失败?

 object o = null; Console.WriteLine(o.ToString()); 

当你考虑它时,请考虑以下,但不会失败:

 public static string MakeString(this object o) { return o == null ? "null" : o.ToString(); } // elsewhere: object o = null; Console.WriteLine(o.MakeString()); 

这让我提出一个问题:如果语言设计者早期想到了扩展方法,那么ToString方法是扩展方法的一部分,以防止不必要的NullPointerExceptions? 一些人认为这是一个糟糕的设计

埃菲尔当时有一个特殊的类NIL代表虚无,但仍然拥有所有基类的方法。 有时我希望C#或Java完全放弃null,就像Bertrand Meyer那样。

结论

像Eiffel和Smalltalk这样的经典语言的广泛方法已被一种非常狭隘的方法所取代。 Java在Object上仍有很多方法,C#只有少数几个。 这当然有利于实现。 在包中保持ToString只是让编程保持清晰和可理解的同时因为它是virtual ,你可以(而且应该!)总是覆盖它,这将使你的代码更容易被理解。

– 阿贝尔 –

编辑:提问者编辑了问题并与IComparable进行了比较, ICloneable可能也是如此。 这些都是非常好的评论,通常认为IComparable应该包含在Object 。 与Java一致,C#具有Equals而不是IComparable ,但是对于Java,C#没有ICloneable (Java有clone() )。

您还声明它仅适用于调试。 好吧,考虑到你需要获得某些东西的字符串版本(设计,没有ext。方法,没有String.Format,但你明白了):

 CarInfo car = new CarInfo(); BikeInfo bike = new BikeInfo(); string someInfoText = "Car " + (car is IStringable) ? ((IStringable) car).ToString() : "none") + ", Bike " + (bike is IStringable) ? ((IStringable) bike).ToString() : "none"); 

并将其与此进行比较。 无论您发现哪个更容易,您都应该选择:

 CarInfo car = new CarInfo(); BikeInfo bike = new BikeInfo(); string someInfoText = "Car " + car.ToString() + ", Bike " + bike.ToString(); 

请记住,语言是让事情更清晰,更容易。 语言的许多部分(LINQ,扩展方法, ToString()??运算符)都是作为便利创建的。 这些都不是必需品,但我们很高兴我们拥有它们。 只有当我们知道如何使用它们时,我们才能找到特征的真正价值(或不是)。

我想补充一些关于为什么.NET的System.Object类定义具有ToString()方法或成员函数的想法,以及之前的调试发布。

由于.NET公共语言运行时(CLR)或执行运行时支持Reflection,因此能够在给定类类型的字符串表示的情况下实例化对象似乎是必不可少且基本的。 如果我没弄错的话,CLR中的所有引用值都是从System.Object派生的,类中的ToString()方法通过Reflection确保其可用性和用法。 在.NET中定义类时,定义和实现IStringable接口并不是必需的,也不是必需的,并且在查询程序集以查找其支持的类类型后,无法确保能够动态创建新实例。

由于2.0,3.0和3.5运行时提供的更高级的.NETfunction(如Generics和LINQ)基于Reflection和动态实例化,更不用说.NET的动态语言运行时(DLR)支持允许.NET实现脚本能够通过字符串类型识别和创建实例的语言(如Ruby和Python)似乎是所有类定义中必不可少的必不可少的函数。

简而言之,如果我们无法识别和命名我们想要实例化的特定类,我们如何创建它? 依赖于具有将类类型作为“人类可读”字符串返回的基类行为的ToString()方法似乎是有意义的。

也许对Jeffrey Ricther和Don Box关于.NET Framework设计和体系结构的文章和书籍的评论也可以提供关于该主题的更好的见解。