Ya know what, I don't know a thing about .NET.
But, I'd like to make an observation.
Most modern String packages have "copy on write" behaviors.
Specifically, that means if you allocate a substring, it will use the existing storage of the parent string, until the string has a need to change, at which point it will copy out the underlying data in to it's own new space for use.
Now, if you have immutable Strings, where the underlying data can not change, there is little reason to NOT do this. There's no way to "write" to an immutable string, so it doesn't even need copy on write functions, just sharing. C++ has mutable strings, so they do copy on write.
For example, Java does this.
Normally this is a good thing. There's little performance impact.
Where you DON'T want this to happen, though, is say in this example:
String big1MBString = readLongHonkinStringFromTheInterTubes();
static String ittyBitty = big1MBString.substring(1, 5);
You now have a "5 character" string that consumes 1MB of memory, because it shares the underlying 1MB string buffer of the large string, but it's manifested as only a 5 character string. Since you retain the reference to the larger String, internally, you'll "never" free up that original space.
Looking at the Mono sources, they do, in fact, allocate new memory. So, perhaps .NET is an exception to what seems to be common practice today. No doubt they have their valid and informed reasons (i.e. I'm not saying .NET did it wrong), just...different from what others are doing.