EDITED: my initial answer simply pointed to IL for two methods; my intention was to suggest that Darren sees them as a starting point. Ani (comments) suggested that this is not enough for a clear answer. I also decided to take a look at this, so I posted here a diagram of the process that I proposed to Darren, which, I hope, shows the process of passing, and shows how others can immediately say "o, x uses the string ::. Format"
So: naively, I expect the first to be more efficient since all CLRs need to combine the two lines, while the second uses a generic method that takes an object and therefore needs to convert this object to a string. Even if this conversion is trivial, you still need to go through some plumbing to get to the call, before finally finding that the string does not need a conversion. I looked at IL using ildasm to see what was happening - equally we could use Reflector , which could potentially be more readable.
For what it's worth - I think I'll use StringBuilder.Append from start, anyway.
In the first instance (+), we have a call to String :: Concat (string, string) (which does pretty much what you would expect if you look at IL), and then a Console.WriteLine (string).
IL_000d: call string [mscorlib] System.String :: Concat (string,
string)
IL_0012: call void [mscorlib] System.Console :: WriteLine (string)
Console.WriteLine (string) effectively just calls TextWriter :: WriteLine (string). So much for the first method.
The second method calls Console.WriteLine (string, object):
IL_000d: call void [mscorlib] System.Console :: WriteLine (string,
object)
If we parse Console :: WriteLine (in mscorlib), we see that it calls TextWriter :: WriteLine (string, object):
.method public hidebysig static void WriteLine (string format,
object arg0) cil managed
...
IL_0007: callvirt instance void System.IO.TextWriter :: WriteLine (string,
object)
which, parsed, calls String :: Format (...):
.method public hidebysig newslot virtual
instance void WriteLine (string format,
object arg0) cil managed
...
IL_0014: call string System.String :: Format (class System.IFormatProvider,
string
In this case, String :: Format actually creates a StringBuilder with the starting string:
.method public hidebysig static string Format (class System.IFormatProvider provider,
string format
...
IL_0027: newobj instance void System.Text.StringBuilder ::. Ctor (int32)
then you need to call StringBuilder :: AppendFormat with the object
IL_0031: callvirt instance class System.Text.StringBuilder System.Text.StringBuilder :: AppendFormat (class System.IFormatProvider,
string
object [])
to fill it out. StringBuilder :: AppendFormat must then convert the object so that it can add it as a string before finally calling TextWriter :: WriteLine (string).
To summarize this batch, both end up calling TextWriter :: WriteLine (string), so they differ in that the first calls
String :: Concat (string, string)
and the second challenge
Console :: WriteLine (string, object),
TextWriter :: WriteLine (string, object)
String :: Format (...).
String :: StringBuilder (). Ctor
String :: StringBuilder (). AppendFormat (...)
(and plumbing) basically do the same job.
There an amazing amount happens under the hood in the second. StringBuilder :: ApendFormat is easily the hardest part of everything, and in fact my IL is not good enough to decide if it has an early exit if, for example, it finds out that the object is passed a string ... but the fact that it owes it to consider, means must do some extra work.
Thus, there are major differences between them. The first looks more if you know that you have two lines to combine, which may be useful. It was interesting to see how the first is compared using StringBuilder from the very beginning.
Comments and corrections are welcome .. and hope this helps clarify my answer.