问题很简单,并在标题中提出。
C#4.0规范说:(§4.2.2)
对象类类型是最终的 所有其他类型的基类。一切 直接或间接输入C# 派生自对象类类型。
接口类型,不是类, 不是来自对象。
现实说:
Type t = typeof(ICloneable).BaseType;
Console.WriteLine(t == null);
真
那么规格错误还是什么?谁相信?
答案 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
的转换:
隐式参考转换是:
- 从任何引用类型到
object
和dynamic
。
答案 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节)。
重要的一点是,接口始终可以被视为对象。那么,它们是否“实际上”是从对象派生的,纯粹是哲学上的讨论。