I first wrote about this here, but I've since received several requests for more prescriptive advice on the subject.  Again you can never be sure without understanding the exact usage pattern because the results really do vary widely but here's some general guidelines that may be useful.

The choice (String vs. StringBuilder) usually turns not on whether the strings are known at compile time or not, but rather on whether the number of appends is known.  This is especially true if you can't really be sure of the sizes in advance either (i.e. they might vary).

e.g.
 
if your pattern looks like:
x = f1(...) + f2(...) + f3(...) + f4(...)
 
that's one concat and it's zippy, StringBuilder probably won't help.
 
if your pattern looks like:
if (...) x += f1(...)
if (...) x += f2(...)
if (...) x += f3(...)
if (...) x += f4(...)
 
then you probably want StringBuilder.
 
Things like generating SQL on the fly for a query probably want StringBuilder, things like making an absolute file path from components probably don't (unless those components could be put into a buffer that's part of some bigger operation, thereby saving temporary strings).
 
Don't underestimate the value of this pattern:
StringBuilder sb;
 
if (...) AppendF1(sb,...)
if (...) AppendF2(sb,...)
if (...) AppendF3(sb,...)
if (...) AppendF4(sb,...)
 
If you pass in the buffer, then the f1 through f4 append functions might not ever need to make temporary string parts, they can stream into the buffer.  And it recurses well.  For large scale string assembly (like making SQL statements on the fly) I find the last pattern very useful.  Reusing the same buffer can be much better than:
 
if (...) sb.Append(f1(...))
if (...) sb.Append(f2(...))
if (...) sb.Append(f3(...))
if (...) sb.Append(f4(...))
Please remember to ignore my advice when you have a thoughtful reason to do so :)