Hi jcoon,
This is a very quick and full of holes but might help up understand a little.
A string is just a array of characters, it is a reference type but Microsoft make them immutable so acts like value type.
All the replace, join, remove, etc.... modifiers do not edit the string but take the data needed from current string and return a new string..
You can actually modify a string through reflection, unsafe mode, etc... but is not a good idea because of the intern pool , strings used as keys, and other reasons you can search for.
So the for each loop in your method the CLR has to allocate memory for the array to hold the characters then copy the characters over and add new ones.
Which will impact performance and take a look here
at this link at the charts in middle of page where the compiler knows the number of iterations and is a literal string is being concatenated and string builder performs much better.
So you could think of it like a group of mailboxes or slots. The concatenation method you have the same number of slots as characters. For each concatenation you have to get a new slot for each of new and old character then take all the characters out of the old ones and move them into the new ones. Repeat, repeat,
You think of the string builder as it starts off with a certain number of slots, and modifies them or adds new characters to empty slots, when it gets full it goes and gets twice as many slots or some factor, so it does not have to go and get new slots and copy characters over for each expression that adds characters.