Fabulous Adventures In Coding
Eric Lippert is a principal developer on the C# compiler team. Learn more about Eric.
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.
ICustomer customer = createCustomer();
customer.GetHashCode();//makes as much sense as having horns on a fish..
Makes all the sense if:
Dictionary<custromer, double> balance = new ...;
In order to use ICustomer (or any other type) you need to have equality and hash code members. There are cases for the other ones, too.
It sounds like what you really want is for Object's methods to be "hidden," most likely at the IDE level. I actually think it would be great to bump up intellisense's intelligence and allow you to sort out some of the more obnoxious inherited members.
At runtime, you may think you don't want those methods, but as sukru pointed out, you probably do, you just don't want to have to think about it.
So you think the fact that someone might want to use object interfaces (or any object for that matter) as a key in a map, puts the onus of providing a hash value on the interface (or class) for that object? So if we invent a new data structure which requires that all objects stored in it have a property (lets call it quantum hash) then all objects should provide GetQuantumHashCode()? How these objects are stored by an external actor in a data structure shouldnt be a reason to change the interface, contract, class or whatever.
Tool-nuisance and cluttering is definitely one aspect of it. Another is that it just does not make any sense to me, especially for interfaces. My comments above make clear why.
I'm surprised you didn't mention delegate types in your discussion..
what about the "void" type? why is not Func<void> allowed?
@Chris: Thanks for the info. I wasn't aware we could specify type parameters at run-time.
@cpun: If you have this much enmity toward a harmless and potentially very useful feature, you're using the wrong language. .NET is designed to simplify things. This is accomplished in part by providing a variety of "default" methods so the user doesn't have to recreate them: Equals(), GetHashCode(), MemberwiseClone(). If you have a problem with this, C++ and STL (or some other combination) is your answer.
If interface is not an Object why does this code print it is an object?
static void Main(string args)
if(typeof(ITest) is Object)
Console.WriteLine("ITest is an Object.");
Console.WriteLine("ITest is not an Object.");
because typeof(ITest) returns an instance of System.Type representing the interface ITest, not the ITest interface itself
@James: "typeof(ITest) is Object" is true because typeof(ITest) returns the instance of System.Type describing ITest. And System.Type derives from Object.
I have no enmity towards .NET/C#. I love the platform and language. But like anything else in this universe, it is not perfect just like its designers. Criticizing aspects of a language does not damn the whole language, just those aspects.
I agree that you have a valid point. However, no design is perfect, and every design is the result of a tradeoff between competing concerns. In this particular case, I tend to prefer the choice made by the CLR designers to include a minimal set of methods in object, rather than leave that class completely empty. It's a pragmatic choice rather than a "pure" choice, but I think that's okay in the context of the problem-space the CLR is aimed at.
In particular, there isn't any additional burden placed on application or library developers by the inclusion of these methods in object. You don't have to provide implementations of GetHashCode() or ToString() if you don't want to - you can just inherit the implementations from object.
GetHashCode() allows any object to be used as a key in a Dictionary, which is a very common and useful pattern. It's very convenient to know that any object can be used this way.
Similarly, ToString() is quite handy, especially in debugging and diagnosic scenarios. Again, knowing that *any* object supports it very convenient.
The alternative design would have been to have interfaces (IHashable or whatever) that you'd strongly encourage people to implement. You'd end up having to explicitly implement a lot more stuff every time you made a class. Or else you wouldn't bother, and then a user of your class would be frustrated that they couldn't use your class as a hash key.
I'm not saying the alternate design is necessarily a bad one. I'm just saying it makes different tradeoffs.
thanks for taking my comments in the spirit they were intended. I have been writing a library based data access DSL @work and since I use interfaces to model the BNF productions the fact that i get these methods ruining my otherwise nice DSL is ticking me off :). But as you said, everything is a trade-off.
What you propose was not the only alternative design. A better approach would have been to make GetHashCode and ToString static members of Object. The implementation would first check for the IHashCodeProvider or IStringProvider interfaces; if they are implemented on the type, use them, otherwise use the built-in implementation. Not only would this have been a more "pure" design, but it would allow type consumers to determine whether or not a class provides its own implementation of these methods. This would be consistent with casting to any other interface to determine if a type has particular capabilites, like IComparable or INotifyPropertyChanged. This could be especially significant for ToString; there have been several times when I have wanted to display a string which represents an object, but if the object does not provide its own string implementation, I do NOT want to display the type name, which is the default implementation of ToString. If there were an IStringProvider interface, I could check for the existence of the interface and if its not there, provide my own implementation (such as a blank string).