接口派生自System.Object吗? C#规范说是的,Eric说不,现实说没有

时间:2010-07-13 10:41:32

标签: c# .net

问题很简单,并在标题中提出。

C#4.0规范说:(§4.2.2)

  

对象类类型是最终的   所有其他类型的基类。一切   直接或间接输入C#   派生自对象类类型。

Eric Lippert says

  

接口类型,不是类,   不是来自对象。

现实说:

Type t = typeof(ICloneable).BaseType;
Console.WriteLine(t == null);
  

那么规格错误还是什么?谁相信?

4 个答案:

答案 0 :(得分:32)

问题并不像你想象的那么简单:)

接口不会从object 派生,但您可以在其上调用object的成员。因此,您可以在编译时类型为ToString()的表达式上调用IDisposable

巧合的是,我在NDC的Neal Gafter和Eric之间进行了一次谈话,正好讨论了这一点......

遗憾的是,我认为规范的第4.2.2节过于简化了。希望Mads和Eric能为将来的版本修复它 - 我会邮寄它们以确保他们看到这个问题。

我也在努力寻找规范中的任何内容来支持这个答案的其余部分。 C#4规范的第3.4.5节尽可能接近我的发现:

  

接口的成员是在接口和接口的所有基接口中声明的成员。严格来说,班级object中的成员不是任何界面的成员(13.2)。但是,类object中的成员可通过任何接口类型(7.4)中的成员查找获得。

6.1.6节中介绍了从接口类型到object的转换:

  

隐式参考转换是:

     
      
  • 从任何引用类型objectdynamic
  •   

答案 1 :(得分:29)

乔恩(像往常一样)现场观看。这并不像你想象的那么容易!

规范含糊不清,略有矛盾。在这种特殊情况下,最好稍微眯一下并获得规范意味着传达的要点,而不是狭隘地解析它以获得精确的定义。

事情的简单事实是“继承”在面向对象编程中是一个非常过度使用的术语。 (我似乎记得C ++有六种不同的继承,但我很难在短时间内将它们全部命名。)

如果我有我的druthers,那么C#规范会清楚地说明继承接口实现之间的区别。继承是*类(和委托)和结构(和枚举)类型的代码共享技术“;它的机制是基类型的所有可归属成员都成为派生类型的成员。这是与接口实现相反,是一个实现类型具有一定公共成员的要求。这两件事在概念上对我来说非常不同;一个是关于共享现有成员,另一个是需要某些成员

然而,规范并没有这样做;它将这两者混合在继承的标题之下。鉴于这两个有些不同的东西在规范中具有相同的名称,很难清楚而准确地推断出它们之间的差异。

我个人更喜欢认为该对象任何接口的“基本类型”,并且对象的成员接口继承。你可以在一个接口实例上调用它们更像是编译器向你扩展的礼貌,这样你就不必在那里插入一个强制转换器。

答案 2 :(得分:2)

接口类型不从Object继承,但接口类型的存储位置保存对类型对象的引用,如果非空,则保证从System.Object继承。

我认为,如果从检查值类型和类类型之间的差异开始,理解正在发生的事情将是最简单的。假设我有一个结构:

public struct SimplePoint {public int x,y;}

我有两种方法

public doSomethingWithPoint(SimplePoint pt) ...
public doSomethingWithObject(Object it) ...

并校准每种方法:

SimplePoint myPoint = ...;
doSomethingWithPoint(myPoint);
dosomethingWithObject(myPoint);

第一个调用不会传递源自Object的内容。它会传递所有SimplePoint的公共和私有字段的内容。第二个调用需要一个派生自Object的东西,因此它生成一个类型为SimplePoint的新堆对象实例,它包含值类型SimplePoint的所有公共和私有字段,以及使用myPoint中的相应值加载所有这些字段,并将引用传递给该对象。

请注意,类型SimplePoint实际上描述了两种不同的东西:字段集合(即值类型)和堆对象类型。哪个含义适用取决于使用类型的上下文。

接口类型具有类似的皱纹:当用作存储位置类型时,它们指定存储位置将包含对象引用。当用作通用约束时,他们没有说明如何存储类型。因此,接口类型的存储位置将保存对真正从System.Object继承的堆对象的引用,但是约束到接口的类型的变量可能包含引用或一堆字段。 / p>

答案 3 :(得分:0)

这实际上取决于“派生词”的定义。令人惊讶的是,C#规范对此术语没有一个规范的定义。幸运的是,仍然可以明确指定代码的实际行为。但这确实意味着有些问题要不拆头发就很难回答!

  • 是否存在System.Object子类型?是的。

  • 接口是否从System.Object进行了继承?不。

subtype的常见定义是,如果类型A是类型B的子类型,则可以在任何需要类型B的值的地方使用类型A的值。完全履行了B的合同。

根据此定义,C#中的所有接口都是子类型System.Object,因为接口实例也是System.Object的实例。

例如,您可以执行以下操作:

 IComparable x = new String();
 Console.Write(x is Object); // writes "true"

这:

IComparable x = new String();
object y = x; // implicit cast from interface to System.Object

和:

IComparable x = new String();
var y = x.ToString(); // method inherited from System.Object

所以它走路像鸭子,嘎嘎像鸭子!

但是Eric Lippert说...

在报价单中,利珀特使用“派生”来指代继承。所有类类型都从System.Object继承(直接或间接)成员,但接口不继承。接口仅从其他接口继承。因此,根据此定义,接口显然不是从System.Object派生的。

但是请注意,这不是关于语言实际工作方式的分歧,纯粹是用来描述它的术语上的差异。

这两个定义在C#中通常是等效的,但是您的问题是最重要的一种情况。

但是现实说...

Reflection是使用CLI术语的独立于语言的.net API。由于C#并不正式取决于CLI,因此无法将其直接传输到C#。 (例如,在CLR中,值类型被认为是与装箱值类型不同的类型,并且仅将装箱值类型视为对象。这不是C#的区别。)

但是无论如何,BaseType属性是这样指定的:

当前Type直接从其继承的Type;如果 当前的Type表示Object类或接口。

请注意,这完全避开了问题!它没有说明接口可以是否具有基本类型,只是无论如何它将始终返回null。因此,它并不能真正回答问题。

底线

最后,重要的是该语言的可观察行为。从较大的引用中可以清楚地看出语言设计者的意图:

C#的类型系统是统一的,因此任何类型的值都可以是 视为对象。 C#中的每种类型都直接或间接派生 从对象类类型开始,object是 所有类型。引用类型的值仅通过以下方式被视为对象 查看类型为object的值。值类型的值被处理 通过执行装箱和拆箱操作将其作为对象(第9.3.12节)。

重要的一点是,接口始终可以被视为对象。那么,它们是否“实际上”是从对象派生的,纯粹是哲学上的讨论。