In a sense, ToString()
does violate the single responsibility principle. Converting to a string representation should not necessarily be a requirement of each and every object type. In C#, GetHashCode()
is arguably worse - and again, defined on every type. This could easily have been done via some other mechanism (ie: an optional interface, and a class with the single responsibility of converting any object into a string, etc).
It's a matter of practicality vs. correctness. Having a method on every object which provides a string representation (arguably) makes some things simpler overall, at the expense of having each object implement it. That being said, objects do not need to take on this responsibility if they are happy with the default implementation.