Classic library design issue
The problem boils down to which classes need to understand which other classes. There are three possible solutions:
1). Put all the convert functions in the String class. This is nice to the extent that you can just call the convert function directly from the object to be converted:
"123.45".toDouble();
The problem from a library design standpoint is that (a). The String class now has to understand all the potential target data formats; and (b). Every time you add a new type, you have to modify the String class to add a conversion function. From languages like Smalltalk, this isn't a problem because the string class can be modified by the add programmers to tack on new functions as needed.
Also, Smalltalk can get away with passing the message on to the class to be converted to. That is, Smalltalk probably just ends up sending a message to the Double Class (in this example) with the string as the message to the double class. That means that the String class doesn't have to know how the string gets converted to a double, it just needs to pass the message on to the double class and let it do the magic.
Problem with languages like Java and C#, though, is that they don't have this dynamic capability, either in terms of adding methods to the base String class or to dispatching the problem on to another target class. As such, both Java and C# don't put the convert methods in the String class. VB does do some compiler magic to do auto converts but it's rather limited and come up to bite you as often as not.
2). Put the convert methods in the target classes. Since you don't have dynamic dispatch like Smalltalk, you have to do the dispatch yourself. With overloading, though, you can have one method in the target class that handles all of the various possible inputs. Something like:
Double.parse("123.45");
This has the advantage that the Double class is the only one that needs to understand how you arrive at a double from other types of data. It has the disadvantage that the Double class still has to understand all the possible source data formats that will be shot it's way. IIRC, this is the technique used by the Java libraries.
3). Seperate the problem of conversion from both the source and target classes. That is, treat conversion as a specialized problem and lump it into a class all by it's lonesome self. Something like:
Convert.parseDouble("123.45");
Has the advantage of keeping the problem out of both the source and target classes (String and Double). Has the disadvantage of having a class that has to understand all the intricacies of conversion for all the n possible from-to combinations. This seems to be the method chosen by C#.