为什么.NET中的所有类都全局继承自Object类?


16

这对我来说非常有趣,哪些优点为框架提供了“全局根类”方法。简单地说,是什么原因导致.NET框架被设计为具有一个根对象类,并且具有适用于所有类的通用功能。

如今,我们正在设计一个供内部使用的新框架(SAP平台下的框架),我们都分为两个阵营-第一个阵营认为框架应该具有全球根源,第二个阵营认为相反。

我在“全球根源”营地。我的理由是,这种方法将产生良好的灵活性并降低开发成本,导致我们不再开发通用功能。

因此,我非常想知道是什么原因真正促使.NET架构师以这种方式设计框架。


6
请记住,.NET Framework是通用软件开发环境的一部分。像您这样的更具体的框架可能不需要“根”对象。object.NET框架中有一个根源,部分原因是它为所有对象提供了一些基本功能,例如ToString()andGetHashCode()
Robert Harvey

10
“我很高兴知道是什么原因真正促使.NET架构师以这种方式设计框架。” 这样做有三个很好的理由:1. Java就是这样做的,2. Java是那样的,3. Java是那样的。
dasblinkenlight 2012年

2
@vcsjones:和Java例如已经污染了wait()/ notify()/ notifyAll()clone()
Joachim Sauer

3
@daskblinkenlight多数民众赞成在大便。Java没有那样做。
但丁2012年

2
@dasblinkenlight:仅供参考,Java是目前正在复制C#的代码,具有lambda,LINQ函数等...
user541686

Answers:


18

最紧迫的原因Object是容器(泛型之前)可以包含任何内容,而不必使用C样式的“为您需要的所有内容再次编写”。当然,可以争辩的是,所有事物都应继承自特定的类,然后滥用这一事实以完全丧失类型安全的所有斑点的想法是如此可怕,以至于在不使用泛型的情况下发布该语言应该是一个巨大的警告,这也意味着Object对于新代码而言,这是完全多余的。


6
这是正确的答案。缺乏通用支持是唯一的原因。其他“通用方法”参数不是真实参数,因为通用方法不必通用-示例:1)ToString:在大多数情况下被滥用;2)GetHashCode:根据程序的功能,大多数类不需要此方法;3)GetType:不一定是实例方法
Codism 2012年

2
.NET通过“滥用”一切都应从特定类继承的想法,以什么方式“彻底失去类型安全的所有斑点”?有Object没有可以用void *C ++ 编写的shitcode ?
Carson63000

3
谁说我认为void*更好?不是。如果有什么。它甚至更糟
DeadMG

void*对于所有隐式指针强制转换,C语言都更糟糕,但是C ++在这方面更严格。至少您必须显式转换。
陶Szelei

2
@fish:一个术语语:在C语言中,没有所谓的“隐式强制转换”。强制转换是用于指定转换的显式运算符。您的意思是“隐式指针转换 ”。
基思·汤普森

11

我记得埃里克·利珀特(Eric Lippert)曾经说过,从System.Object类中继承提供“为客户带来最大价值”。[编辑:是的,他在这里说过 ]:

...它们不需要通用的基本类型。此选择并非出于必要。出于为客户提供最佳价值的愿望。

在设计类型系统或与此相关的任何其他东西时,有时您会遇到决策点-您必须决定X还是不决定X ...拥有通用基本类型的好处超过成本,从而带来净收益产生的收益大于没有通用基本类型的净收益。因此,我们选择具有通用的基本类型。

这是一个非常模糊的答案。如果您需要更具体的答案,请尝试提出更具体的问题。

让一切都源于System.Object课堂,这提供了值得信赖的可靠性和实用性。我知道所有对象都将具有类型(GetType),它们将符合CLR的终结方法(Finalize),并且GetHashCode在处理集合时可以在它们上使用。


2
这是有缺陷的。您不需要GetType,只需扩展typeof即可替换其功能。至于Finalize,实际上,许多类型根本无法完全终结,并且没有有意义的Finalize方法。另外,许多类型不能散列或转换为字符串,尤其是内部类型,它们永远都不打算用于此类用途,也永远不会被公开使用。所有这些类型的接口都被不必要地污染了。
DeadMG

5
不,它不是有缺陷的-我从来没有说你会使用GetTypeFinalizeGetHashCode为每一个System.Object。我只是说如果你想要他们的话,他们在那里。至于“它们的接口受到不必要的污染”,我将参考我的elippert原始报价:“为客户带来最高价值”。显然,.NET团队愿意解决这些折衷。
gws2 2012年

如果没有必要,那就是界面污染。“如果您愿意”绝不是包含方法的充分理由。它只应该在那儿,因为您需要它,而您的消费者会称之为它。否则,这不是很好。同样,您的利珀特语录也吸引了权威的谬论,因为也只说“很好”。
DeadMG

我不是要争辩您的观点@DeadMG-问题特别是“为什么.NET中的所有类在全局范围内都继承自Object类”,因此我链接到上一个非常接近.NET团队的人的帖子。微软给出了一个合法的答案,尽管它含糊其词,但它却说明了.NET开发团队为什么选择这种方法。
gws2 2012年

太含糊,无法回答这个问题。
DeadMG

4

我认为Java和C#设计人员添加了一个根对象,因为它对他们基本上是免费的,例如免费午餐

添加根对象的成本与添加第一个虚拟函数的成本非常相等。由于CLR和JVM都是带有对象终结器的垃圾回收环境,因此您需要java.lang.Object.finalize为您的System.Object.Finalize方法或方法至少拥有一个虚拟。因此,添加根对象的成本已经预先支付,您无需支付即可获得所有收益。这是两全其美的方法:需要通用根类的用户将获得所需的东西,而不太在乎的用户则可以像不存在它那样进行编程。


4

在我看来,这里有三个问题:一个是为什么将通用根引入到.NET,第二个是它的优点和缺点是什么,第三个是使框架元素具有全局性是否是一个好主意根。

.NET为什么有共同的根?

从最技术的角度来看,拥有共同的根对于反射和前代容器至关重要

另外,据我所知,与Java和这类基本方法有着共同的根源,equals()并且hashCode()在Java中获得了肯定,并且C#受(尤其是)Java的影响,因此他们也希望拥有该功能。

在语言中拥有共同的根源有什么优点和缺点?

具有共同根的优势:

  • 您可以允许所有对象具有您认为重要的某些功能,例如equals()hashCode()。举例来说,这意味着Java或C#中的每个对象都可以在哈希图中使用-将这种情况与C ++进行比较。
  • 您可以引用未知类型的对象-例如,如果您像以前的容器那样只是传递信息。
  • 您可以引用未知类型的对象-例如,在使用反射时。
  • 在极少数情况下,当您希望能够接受具有不同或其他不相关类型的对象(例如,作为类似printf方法的参数)时,可以使用它。
  • 它使您只需更改一个类即可灵活地更改所有对象的行为。例如,如果要向C#程序中的所有对象添加方法,则可以向添加扩展方法object。也许不是很普遍,但是如果没有共同的根源,这将是不可能的。

具有共同根的缺点:

  • 即使您可以使用更准确的类型来引用具有一定价值的对象,也可能会滥用该对象。

您是否应该在框架项目中使用通用根?

当然,这非常主观,但是当我看一下上面的赞成列表时,我肯定会回答yes。具体来说,专业版列表中的最后一个项目符号–稍后通过更改根来更改所有对象的行为的灵活性–在一个平台中非常有用,在该平台中,对根的更改比整个更改更容易被客户端接受语言。

此外,尽管这更加主观,但我发现通用词根的概念非常优雅和吸引人。这也使某些工具的使用更加容易,例如,现在很容易要求一个工具显示该公共根的所有后代,并快速,全面地了解框架的类型。

当然,类型也必须至少略微相关的是什么,以及特别是,我从来没有牺牲“是”的规则,只是为了有一个共同的根源。


我认为c#不仅受Java的影响,而且在某种程度上是Java。微软不再使用Java,因此将放弃数十亿美元的投资。他们从提供他们已经有一个新名称的语言开始。
Mike Braun

3

从数学上讲,拥有一个包含top的类型系统要稍微优雅一点,它允许您更完全地定义一种语言。

如果要创建框架,那么现有的代码库肯定会消耗掉所有东西。您不能拥有一个通用的超类型,因为所有消耗框架的其他类型都存在。

那时决定是否拥有通用基类取决于您在做什么。这是非常罕见的,很多事情都有一个共同的行为,认为它是有用的通过单独的共同的行为来引用这些事情。

但是它发生了。如果是这样,则继续抽象该常见行为。


2

之所以推送根对象,是因为他们希望框架中的所有类都支持某些东西(获取哈希码,转换为字符串,相等性检查等)。C#和Java都发现将某个对象的所有这些通用功能放在某个根类中很有用。

请记住,它们没有违反任何OOP原则或任何其他内容。Object类中的任何内容在系统中的任何子类(读取:任何类)中都是有意义的。如果选择采用这种设计,请确保遵循此模式。也就是说,不要在根目录中包含不属于系统中每个单一类的内容。如果您认真遵循此规则,那么我不认为您不应该拥有一个包含整个系统有用的通用代码的根类。


2
那根本不是真的。有许多仅用于内部的类,它们从不散列,从不反映,从不转换为字符串。不必要的继承和多余的方法确实确实违反了许多原则。
DeadMG

2

有几个原因,所有子类都可以共享通用功能。而且,它还使框架作者能够将很多其他功能写入可以使用的框架。一个示例就是ASP.NET缓存和会话。几乎所有内容都可以存储在其中,因为他们编写了add方法来接受对象。

根类可能很诱人,但很容易被滥用。可以有一个根接口吗?并且只有一种或两种小方法?还是会增加比框架所需的代码更多的代码?

我想问一下,对于在编写框架中的所有可能的类中需要使用哪些功能,我感到好奇。所有对象都会真正使用它吗?还是大多数?创建根类后,如何防止人们向其添加随机功能?还是他们希望成为“全局”的随机变量?

几年前,有一个很大的应用程序在其中创建了一个根类。经过几个月的开发,它充满了没有业务的代码。我们正在转换一个旧的ASP应用程序,并且根类是我们过去使用的旧的global.inc文件的替换。不得不艰难地学习这一课。


2

所有独立的堆对象都继承自Object; 这是有道理的,因为所有独立堆对象必须具有某些共同的方面,例如识别其类型的方法。否则,如果垃圾收集器对未知类型的堆对象进行了引用,则它将无法知道与该对象关联的内存blob中的哪些位应被视为对其他堆对象的引用。

此外,在类型系统内,使用相同的机制定义结构的成员和类的成员很方便。值类型存储位置(变量,参数,字段,数组插槽等)的行为与类类型存储位置的行为有很大不同,但是这种行为差异是在源代码编译器和执行引擎(包括JIT编译器),而不是在类型系统中表示。

这样的结果是,定义值类型实际上定义了两种类型-存储位置类型和堆对象类型。前者可以隐式转换为后者,后者可以通过类型转换转换为前者。两种类型的转换都通过将所有公共字段和私有字段从相关类型的一个实例复制到另一实例来工作。此外,可以使用通用约束直接在值类型存储位置上调用接口成员,而无需先复制它。

所有这些都很重要,因为对值类型堆对象的引用的行为类似于类引用,而不是值类型。例如,考虑以下代码:

字符串testEnumerator <T>(T it)其中T:IEnumerator <string>
{
  var it2 = it;
  it.MoveNext();
  it2.MoveNext();
  返回它。
}
公共无效测试()
{
  var theList = new List <string>();
  theList.Add(“ Fred”);
  theList.Add(“ George”);
  theList.Add(“ Percy”);
  theList.Add(“ Molly”);
  theList.Add(“ Ron”);

  var enum1 = theList.GetEnumerator();
  IEnumerator <string> enum2 =枚举1;

  Debug.Print(testEnumerator(enum1));
  Debug.Print(testEnumerator(enum1));
  Debug.Print(testEnumerator(enum2));
  Debug.Print(testEnumerator(enum2));
}

如果将testEnumerator()方法传递给值类型的存储位置,it则将收到一个实例,该实例的公共和私有字段是从传入的值中复制的。本地变量it2将保存另一个实例,其所有字段均从复制it。调用MoveNextit2不会影响it

如果将上面的代码传递给类类型的存储位置,则传入的值,itit2将全部引用同一个对象,因此MoveNext()对它们中的任何一个进行调用都将对它们全部有效地对其进行调用。

请注意,强制List<String>.Enumerator转换IEnumerator<String>有效地将其从值类型转换为类类型。堆对象的类型是,List<String>.Enumerator但是其行为与同名的值类型有很大不同。


+1(不提及ToString())
JeffO 2012年

1
这都是关于实现细节的。无需向用户公开这些内容。
DeadMG

@DeadMG:你什么意思?以a List<T>.Enumerator形式存储的a的可观察行为与以a 形式存储的a List<T>.Enumerator的行为显着不同IEnumerator<T>,因为将前一种类型的值存储到任一类型的存储位置将复制枚举器的状态,当将后一种类型的值存储到也是后一种类型的存储位置时,将不会进行复制。
2012年

是的,但Object没有做这个事实。
DeadMG

@DeadMG:我的观点是,类型的堆实例System.Int32也是的实例System.Object,但是类型的存储位置System.Int32既没有System.Object实例也没有对实例的引用。
2012年

2

这种设计确实可以追溯到Smalltalk,我将其视为在几乎没有任何其他所有顾虑的情况下追求对象定向的尝试。因此,即使其他技术可能(甚至可以肯定)优越,它也倾向于使用面向对象。

Object根目录具有一个(或类似的东西)单一层次结构使得将您的集合类创建为的集合相当容易(例如)Object,因此对于集合而言,包含任何种类的对象都是微不足道的。

作为这种次要优势的回报,尽管如此,您仍然会遇到很多劣势。首先,从设计角度来看,您最终会得到一些真正疯狂的想法。至少从Java的宇宙观来看,无神论和森林有什么共同点?他们俩都有哈希码!地图是收藏吗?根据Java的说法,不是,不是!

在上世纪70年代设计Smalltalk时,这种废话被接受了,主要是因为没有人设计出合理的替代方案。不过,Smalltalk于1980年完成,并于1983年设计了Ada(包括泛型)。尽管Ada从未达到某些人预测的受欢迎程度,但其泛型足以支持任意类型的对象的集合- 而不是整体式体系结构固有的精神错乱。

在设计Java(在较小程度上是.NET)时,单片类层次结构可能被视为“安全”的选择-存在问题,但大多数是已知问题。相比之下,泛型编程是几乎每个人(甚至那时)都意识到的,至少从理论上讲是一种更好的解决问题的方法,但是许多以商业为导向的开发人员都认为,这种方法的探索性和/或风险较低(例如,在商业世界中) ,Ada因失败而在很大程度上被驳回)。

不过,让我说清楚一点:单一的层次结构是一个错误。该错误的原因至少是可以理解的,但无论如何都是一个错误。这是一个糟糕的设计,几乎所有使用它的代码都存在设计问题。

但是,对于当今的新设计而言,没有一个合理的问题:使用整体式层次结构是一个明显的错误,也是一个坏主意。


值得指出的是,在.NET发行之前,模板就已经很棒并且非常有用。
DeadMG

在商业世界里,艾达一个失败,与其说是由于它的冗长,但由于缺乏规范的接口,操作系统的服务。没有文件系统,图形,网络等的标准API,使得Ada开发“托管”应用程序的成本非常高。
凯文·克莱恩

@kevincline:我可能应该说出一些不同的措辞-表示Ada整体由于其市场失败而被视为失败。(IMO)拒绝使用Ada是很合理的-但拒绝向Ada学习最多(最好)。
杰里·科芬

@Jerry:我认为C ++模板采用了Ada泛型的思想,然后通过隐式实例化对其进行了很大的改进。
凯文·克莱恩

1

您实际上想做的事情很有趣,在完成之前很难证明它是对还是错。

以下是一些需要考虑的事项:

  • 您何时会故意将此顶级对象传递给更特定的对象?
  • 您打算在每个班级中放什么代码?

这是可以从共享根受益的仅有的两件事。在一种语言中,它用于定义一些非常常用的方法,并允许您传递尚未定义的对象,但是这些对象不适用于您。

另外,根据个人经验:

我曾经使用过由背景为Smalltalk的开发人员编写的工具包。他做到了,因此他所有的“数据”类都扩展了一个类,并且所有方法都采用了该类-到目前为止还不错。问题在于不同的数据类并非总是可以互换的,所以现在我的编辑器和编译器无法对在给定情况下要传递的内容提供任何帮助,我不得不参考他的文档,但这种文档并不总是有用的。 。

那是我不得不处理的最难使用的库。


0

因为这就是OOP的方式。具有可以引用任何内容的类型(对象)很重要。类型为object的参数可以接受任何内容。还有继承,您可以确保ToString(),GetHashCode()等在任何事物上都可用。

甚至Oracle都意识到拥有这种基本类型的重要性,并计划在2017年左右删除Java中的原语


6
它既不是OOP的方法,也不是重要的。除了“这很好”外,您不提供任何实际参数。
DeadMG

1
@DeadMG您可以阅读第一句之后的参数。基元的行为不同于对象,因此迫使程序员为对象和基元编写相同目的代码的许多变体。
但丁2012年

2
@DeadMG好,他说过,这意味着您可以假设ToString()并且GetType()会出现在每个对象上。可能值得指出的是,您可以使用类型的变量object来存储任何.NET对象。
JohnL 2012年

另外,完全有可能编写一个用户定义的类型,该类型可以包含任何类型的引用。您不需要特殊的语言功能即可实现。
DeadMG

您只应在一个包含特定代码的级别上拥有一个Object,而永远不应仅拥有一个将您的图形联系在一起的对象。实际上,“对象”实现了一些重要的方法,并将其作为将对象传递到尚未创建类型的工具的一种方式。
Bill K
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.