There's indeed little use of having the Type.FullName
returned to you, but it would be even less use if an empty string or null were returned. You ask why it exists. That's not too easy to answer and has been a much debated issue for years. More then a decade ago, several new languages decided that it would be convenient to implicitly cast an object to a string when it was needed, those languages include Perl, PHP and JavaScript, but none of them is following the object orientation paradigm thoroughly.
Approaches
Designers of object oriented languages had a harder problem. In general, there were three approaches for getting the string representation of an object:
- Use multiple inheritance, simply inherit from
String
as well and you can be cast to a string
- Single inheritance: add
ToString
to the base class as a virtual method
- Either: make the cast operator or copy constructor overloadable for strings
Perhaps you'd ask yourself Why would you need a ToString
or equiv. in the first place? As some others already noted: the ToString
is necessary for introspection (it is called when you hover your mouse over any instance of an object) and the debugger will show it too. As a programmer, you know that on any non-null object you can safely call ToString
, always. No cast needed, no conversion needed.
It is considered good programming practice to always implement ToString
in your own objects with a meaningful value from your persistable properties. Overloads can help if you need different types of representation of your class.
More history
If you dive a bit deeper in the history, we see SmallTalk taking a wider approach. The base object has many more methods, including printString
, printOn
etc.
A small decade later, when Bertrand Meyer wrote his landmark book Object Oriented Software construction, he suggested to use a rather wide base class, GENERAL
. It includes methods like print
, print_line
and tagged_out
, the latter showing all properties of the object, but no default ToString
. But he suggests that the "second base object ANY to which all user defined object derive, can be expanded", which seems like the prototype approach we now know from JavaScript.
In C++, the only multiple inheritance language still in widespread use, no common ancestor exists for all classes. This could be the best candidate language to employ your own approach, i.e. use IStringable
. But C++ has other ways: you can overload the cast operator and the copy constructor to implement stringability. In practice, having to be explicit about a to-string-implementation (as you suggest with IStringable
) becomes quite cumbersome. C++ programmers know that.
In Java we find the first appearance of toString
for a mainstream language. Unfortunately, Java has two main types: objects and value types. Value types do not have a toString
method, instead you need to use Integer.toString
or cast to the object counterpart. This has proven very cumbersome throughout the years, but Java programmers (incl. me) learnt to live with it.
Then came C# (I skipped a few languages, don't want to make it too long), which was first intended as a display language for the .NET platform, but proved very popular after initial skepticism. The C# designers (Anders Hejlsberg et al) looked mainly at C++ and Java and tried to take the best of both worlds. The value type remained, but boxing was introduced. This made it possible to have value types derive from Object implicitly. Adding ToString
analogous to Java was just a small step and was done to ease the transition from the Java world, but has shown its invaluable merits by now.
Oddity
Though you don't directly ask about it, but why would the following have to fail?
object o = null;
Console.WriteLine(o.ToString());
and while you think about it, consider the following, which does not fail:
public static string MakeString(this object o)
{ return o == null ? "null" : o.ToString(); }
// elsewhere:
object o = null;
Console.WriteLine(o.MakeString());
which makes me ask the question: would, if the language designers had thought of extension methods early on, the ToString method be part of the extension methods to prevent unnecessary NullPointerExceptions? Some consider this bad design, other consider it a timesaver.
Eiffel, at the time, had a special class NIL
which represented nothingness, but still had all the base class's methods. Sometimes I wished that C# or Java had abandoned null altogether, just like Bertrand Meyer did.
Conclusion
The wide approach of classical languages like Eiffel and Smalltalk has been replaced by a very narrow approach. Java still has a lot of methods on Object, C# only has a handful. This is of course good for implementations. Keeping ToString
in the package simply keeps programming clean and understandable at the same time and because it is virtual
, you can (and should!) always override it, which will make your code better apprehendable.
-- Abel --
EDIT: the asker edited the question and made a comparison to IComparable
, same is probably true for ICloneable
. Those are very good remarks and it is often considered that IComparable
should've been included in Object
. In line with Java, C# has Equals and not IComparable
, but against Java, C# does not have ICloneable
(Java has clone()
).
You also state that it is handy for debugging only. Well, consider this everywhere you need to get the string version of something (contrived, no ext. methods, no String.Format, but you get the idea):
CarInfo car = new CarInfo();
BikeInfo bike = new BikeInfo();
string someInfoText = "Car " +
(car is IStringable) ? ((IStringable) car).ToString() : "none") +
", Bike " +
(bike is IStringable) ? ((IStringable) bike).ToString() : "none");
and compare that with this. Whichever you find easier you should choose:
CarInfo car = new CarInfo();
BikeInfo bike = new BikeInfo();
string someInfoText = "Car " + car.ToString() + ", Bike " + bike.ToString();
Remember that languages are about making things clearer and easier. Many parts of the language (LINQ, extension methods, ToString()
, the ??
operator) are created as conveniences. None of these are necessities, but sure are we glad that we have them. Only when we know how to use them well, we also find the true value of a feature (or not).