Not everything derives from object

Not everything derives from object

Rate This
  • Comments 47

I hear a lot of myths about C#. Usually the myths have some germ of truth to them, like "value types are always allocated on the stack". If you replace "always" with "sometimes", then the incorrect mythical statement becomes correct.

One I hear quite frequently is "in C# every type derives from object". Not true!

First off, no pointer types derive from object, nor are any of them convertible to object. Unsafe pointer types are explicitly outside of the normal type rules for the language. (If you want to treat a pointer as a value type, convert it to System.IntPtr, and then you can use it as a value type.) We'll leave pointer types aside for the rest of this discussion.

A great many types do derive from object. All value types, including enums and nullable types, derive from object. All class, array and delegate types derive from object.

These are all the "concrete" types; every object instance that you actually encounter in the wild will be either null, or a class, delegate, array, struct, enum or nullable value type. So it would be correct, though tautological, to say that all object instances derive from object. But that wasn't the myth; the myth stated was that all types are derived from object.

Interface types, not being classes, are not derived from object. They are all convertible to object, to be sure, because we know that at runtime the instance will be a concrete type. But interfaces only derive from other interface types, and object is not an interface type.

"Open" type parameter types are also not derived from object. They are, to be sure, types. You can make a field whose type is a type parameter type. And since generic types and methods ultimately are only ever used at runtime when fully "constructed" with "concrete" type arguments, type parameter types are always convertible to object. (This is why you cannot make an IEnumerable<void*> -- we require that the type argument be convertible to object.) Type parameter types are not derived from anything; they have an "effective base class", so that type arguments are constrained to be derived from the effective base class, but they themselves are not "derived" from anything.

The way to correct this myth is to simply replace "derives from" with "is convertible to", and to ignore pointer types: every non-pointer type in C# is convertible to object.

  • @cpun

    Kzu have a blog post about how you can hide object memebers in interface based dsl:s here: http://www.clariusconsulting.net/blogs/kzu/archive/2008/03/10/58301.aspx

    As a side note IntelliSense in VB.Net these methods are infact NOT callable on an object of an interface type, you have to upcast to Object to use them.

  • I think the practical reality of an empty or nearly-empty "object" implementation would have been a wildly fragmented spinoff of innumerable very similar "rollup" classes that tried to ramp up a minimalist set of basic functionality, but all subtely mutually incompatible, or at worst 90% reinventions of very similar wheels. I think the design choice was the practical one, if not the philosophically pure one.

  • @Patrik

    Thank you! you are awesome.

  • @tony cox

    For an object to be "usable" as an hash key, you strictly need to override the Equals() and GetHashCode() methods, as the default ones are semantically "strange" for most use cases, so - in effect - you gain nothing but the danger that you override one and forget the other, and so get an inconsisten behaviour.

    And why this preference to Hashable objects, and not Comparable, so sorting, trees etc. are ruled out?

    @Tom: I'll investigate this myself.

  • @Tom: Now, one other of my coworkers told me that the WCF Proxies are interface implementations which cannot be cast to Object.

    I'll investigate this myself...

  • Could anyone tell me why ICloneable.Equals(a, b) works?

    Does this imply any relation between interface and Object?

    or there is something behind the scene?

  • @leo:  That's a good question.  Apparently the compiler knows that *every* object will inherit these static methods, regardless of type, so it allows Object's static methods to apply to explicitly declared interfaces in this context.  After all, any class that implements ICloneable will be an Object.  Clearly it's a rule that applies only to Object, since you can't declare an interface that inherits from a class (not even Object!).

  • @leo, it works because C# language spec requires it to; 7.3 "Member lookup":

    "A member lookup of a name N with K type parameters in a type T is processed as follows:

    * If T is a type parameter, then the set is the union of the sets of accessible members named N in each of the types specified as a primary constraint or secondary constraint (§10.1.5) for T, along with the set of accessible members named N in Object.

    * Otherwise, the set consists of all accessible (§3.5) members named N in T, including inherited members and the accessible members named N in Object. If T is a constructed type, the set of members is obtained by substituting type arguments as described in §10.3.2. Members that include an override modifier are excluded from the set."

    Note how it specifically calls for looking up members in Object, and at the same time makes a distinction between "inherited members" and "members in Object".

    What is the rationale for this when T is an interface type is a different question, but it is understandable. After all, if you can call instance members of Object on a value of interface type as if they were "inherited" from Object, it would only be logical to extend the same pseudo-inheritance to static members.

  • Even the authors of C# Language Specification were deceived by this myth. :-)

    Section 4.2.2 says: "The object class type is the ultimate base class of all other types . Every type in C# directly or indirectly derives from the object class type."

    Yep. Mads is aware of the error and will correct the spec in the next version. -- Eric

    Actually, there are some very special value types (like System.TypedReference) that are not convertible to object.

    Yep. One of these days I'll do a blog post about those very special types. That will at the very least force me to figure out how they work. :-) -- Eric
  • http://msdn.microsoft.com/tr-tr/library/ms173156(en-us).aspx  this article says "An interface can itself inherit from multiple interfaces.". so, how about that?

  • Also, this myth is sustained by Jeff Richter's book "CLR via C#, 2nd ed", there is section called "All types are derived from System.Object" in Chapter 4 "Type Fundamentals" of the book.

  • What about the "null type"? (see C# Language Specification §9.4.4.6, §11.2.7)

    We decided during the C# 3 design and specification process that having a special type that only contains one value, null, was a bad idea. It seems plausible, and other language specs also do it this way, but ultimately it adds very little value. The null type is quite unusual in that its the only type that doesn't have a CLR type object associated with it. It is conceptually easier to simply have some expressions be typeless, and be OK with that, than to invent an ad-hoc type solely for the purpose of being able to say what the type of the null literal expression is. This language should have been removed from the C# 3 spec. -- Eric

  • Any idea why following code does not generate compile time error?

       public interface ITest
       {
           void PrintMessage();
       }
      // Class does not implement ITest interface.
       public class BaseClass
       {
       }

      class Program
       {
           static void Main(string[] args)
           {
               BaseClass bclass = new BaseClass();
               // Why compiler does not generate error here....
               ((ITest)bclass).PrintMessage();
           }
       }

    First off, of course it generates an error at runtime. But why not at compile time?

     Suppose we rewrite your program like this:

              BaseClass bclass = GetSomething();
              ((ITest)bclass).PrintMessage();

    Do you agree that the compiler cannot produce an error here? Since BaseClass is not sealed, there could be a derived class that implements ITest, and an instance of it could be returned by GetSomething.

    The compiler is not smart enough to deduce that bclass is always of type BaseClass and never of a more derived class on the codepath that has the cast operator. We do flow analysis, but only for the purposes of determining reachability and definite assignment, not for analyzing the possible runtime types of variables. We could implement such a system, but it would be a lot of work for a small gain.

    -- Eric

  • 13.2 "The members of class object are not, strictly speaking, members of any iinterface."

    How about this one?

    interface MyInterface

       {

           bool Equals(object obj);

           int GetHashCode();

           Type GetType();

           string ToString();

       }

       class MyClass : MyInterface

       {

            // MyInterface is implicitly implemented  because MyClass implicitly derives from type object.

      }

    Isn't it really the case that the members of type object are implicitly members of EVERY interface?

  • The ability to call System.Object's members is a C# language implementation decision as opposed to the implementation of a language like VB.Net(@Patrick upcast to access System.Object members).

    Calling ToString() on an instance of an object implementing an interface (Iface) produces IL as such:

    L_000f: callvirt instance string [mscorlib]System.Object::ToString()

    Specifying "string ToString();" on the Interface changes the IL such:

    L_000f: callvirt instance string Iface::ToString()

    So as the author states the interface isn't inheriting those members from System.Object, it's the compiler that decides to resolve those members to the System.Object class because its a resonable assumption to make that any type that implements Iface is convertable to an object.

Page 3 of 4 (47 items) 1234