In Java and C#, all classes derive from the root class Object
(java.lang.Object
and System.Object
respectively).
In both languages/libraries this class comes with a set of methods that are inherited by every class.
My question is: Why and when do designers add a method to the global root object class?
I'm asking because many of Object
's current methods seem to me like design mistakes, primarily violations of the Separation-of-Concerns rule - but I trust the language and library designers to put a great deal of thought into their designs; do the designers now say it was a mistake to add those methods?
System.Object
Equals(Object)
- Object reference-equality can be tested using
Object.ReferenceEquals
. Value-equality must be explicitly opted-into by overriding this method, but consumers won't know if value-equality is actually defined. We also haveIEquatable<T>
- surely it makes more sense to not have any.Equals
method and instead to always test forIEquatable<T>
?- And in most cases consumers know the types they're comparing so type-checking for
IEquatable<T>
is unnecessary and the compiler can make theIEquatable<T>.Equals(T)
call directly (and as a bonus there's no need to do a runtime type test of theObject
argument value) - And why define
bool Equals
instead of the more generalint Compare
? While value-comparison operations are undefined for many types of objects, the same can be said for value-equality. It seems odd to single-out this particular method.
- And in most cases consumers know the types they're comparing so type-checking for
- Object reference-equality can be tested using
GetHashCode()
- Not every type is used as a hash-table key. This method is essentially useless unless overridden, especially if the object is mutable. As with
Equals
, why not defineIHashable
?- Having an explicit
IHashable
means that hashtable types could use it as a generic type constraint on dictionary key types to help prevent the use of the defaultGetHashCode()
implementation where it would introduce bugs.
- Having an explicit
- And the BCL's hash-table types allow the use of a custom hash-code generator through
IEqualityComparer<T>
(and allstruct
types can have a trivial compiler-generated method).
- Not every type is used as a hash-table key. This method is essentially useless unless overridden, especially if the object is mutable. As with
ToString()
- While handy for debugging, we're better served with
[DebuggerDisplay]
instead, and most classes don't override it (the default implementation returns the type's name). Additionally we already haveIConvertable.ToString()
for when it it's meaningful.
- While handy for debugging, we're better served with
MemberwiseClone()
- This method strikes me as being dangerous, especially if the class' author didn't consider it being called: consider calling
SqlConnection::MemberwiseClone()
on an active connection. And again, the BCL hasIClonable.Clone
.
- This method strikes me as being dangerous, especially if the class' author didn't consider it being called: consider calling
In Java, the situation is similar for its methods clone()
, equals(Object)
, hashCode()
and toString()
- but Java also adds threading primitives directly to Object
instead of in the threading library. This seems shortsighted given that threading libraries can (and do) change their designs more often than the design of the root Object
should be changed.