string is a reference type, but with one crucial twist: it's immutable. No method on string ever changes it in place — every operation returns a brand-new string.
string s1 = "Hello";
string s2 = s1;
s2 += " World";
Console.WriteLine(s1); // "Hello" — unchanged
s2 += " World" doesn't mutate the string s1 points to — it creates a new string object and repoints s2 at it, leaving s1's original string untouched. The practical consequence: s.Trim(); on its own line does nothing observable, because the returned (trimmed) string is discarded. You have to capture it: s = s.Trim();.
Immutability is also why string is a natural choice for dictionary keys and safe to share freely across threads without locking — nothing can ever change a string out from under a reader. But it comes at a performance cost in code that builds up a string incrementally, since each += allocates an entirely new string and copies the old contents into it:
// O(n²) in the length of the final string — each += reallocates and copies everything so far
string result = "";
for (int i = 0; i < 10000; i++)
{
result += i.ToString();
}
// The fix: StringBuilder mutates an internal buffer in place, appending in amortized O(1) per call
var sb = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
sb.Append(i);
}
string result2 = sb.ToString();