8
\$\begingroup\$

I am trying to standardize the way I write code. Which is the best way to place the values into a string, method 1 or 2?

string myCat = "persian";

// Method 1
Console.WriteLine("I have a wild {0} cat that likes to visit", myCat);

// Method 2
Console.WriteLine("I have a wild " + myCat + " cat that likes to visit");

I would like to point out that I am intending this for simple uses, where StringBuilder would be overkill.

\$\endgroup\$
  • 3
    \$\begingroup\$ Not too sure about on-topicness of this question. This isn't a code review, it's asking about the best way to concatenate strings, which I believe would be a better fit on Programmers.SE. \$\endgroup\$ – Mathieu Guindon Nov 6 '13 at 21:16
  • 1
    \$\begingroup\$ What about string.concat or list.join ... etc. etc. I think the best option is to understand the underlying mechanics of what you are writing and choose the right tool. Check out: joelonsoftware.com/articles/fog0000000319.html \$\endgroup\$ – James Khoury Nov 7 '13 at 3:23
  • \$\begingroup\$ sometimes we just have to remember - Less is More :-) Choose option that is more readable unless it is much less efficient, but for small inefficiencies it is better to choose readability over efficiency. \$\endgroup\$ – AquaAlex Nov 7 '13 at 8:27
7
\$\begingroup\$

For anyone who is curious, here is the IL for the OP's example.

IL_0001: ldstr "persian"
IL_0006: stloc.0
IL_0007: ldstr "I have a wild {0} cat that likes to visit"
IL_000c: ldloc.0
IL_000d: call string [mscorlib]System.String::Format(string, object)
IL_0012: call void [mscorlib]System.Console::WriteLine(string)
IL_0017: nop
IL_0018: ldstr "I have a wild "
IL_001d: ldloc.0
IL_001e: ldstr " cat that likes to visit"
IL_0023: call string [mscorlib]System.String::Concat(string, string, string)
IL_0028: call void [mscorlib]System.Console::WriteLine(string)

Personally I opt for String.Format() because it does so much for so little.

Consider this example:

Console.WriteLine("I have " + count.ToString() + " wild cats that like to visit");

Which results with this IL:

IL_0030: ldstr "I have "
IL_0035: ldloca.s count
IL_0037: call instance string [mscorlib]System.Int32::ToString()
IL_003c: ldstr " wild cats that like to visit"
IL_0041: call string [mscorlib]System.String::Concat(string, string, string)
IL_0046: call void [mscorlib]System.Console::WriteLine(string)

In comparison, using String.Format

Console.WriteLine(String.Format("I have {0} wild cats that like to visit", count));

...is slightly better.

IL_0096: ldstr "I have {0} wild cats that like to visit"
IL_009b: ldloc.1
IL_009c: box [mscorlib]System.Int32
IL_00a1: call string [mscorlib]System.String::Format(string, object)
IL_00a6: call void [mscorlib]System.Console::WriteLine(string)

Not convinced? How about this realistic example with two substitution parameters including a date (I'm often inserting a date into log strings, for example).

The bad way:

Console.WriteLine("Today is " + System.DateTime.Now.ToString("M/d/yy") + " and I have " + count.ToString() + " wild cats that like to visit");

Insane block of IL this time:

IL_004c: ldc.i4.5
IL_004d: newarr [mscorlib]System.String
IL_0052: stloc.2
IL_0053: ldloc.2
IL_0054: ldc.i4.0
IL_0055: ldstr "Today is "
IL_005a: stelem.ref
IL_005b: ldloc.2
IL_005c: ldc.i4.1
IL_005d: call valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now()
IL_0062: stloc.3
IL_0063: ldloca.s CS$0$0001
IL_0065: ldstr "M/d/yy"
IL_006a: call instance string [mscorlib]System.DateTime::ToString(string)
IL_006f: stelem.ref
IL_0070: ldloc.2
IL_0071: ldc.i4.2
IL_0072: ldstr " and I have "
IL_0077: stelem.ref
IL_0078: ldloc.2
IL_0079: ldc.i4.3
IL_007a: ldloca.s count
IL_007c: call instance string [mscorlib]System.Int32::ToString()
IL_0081: stelem.ref
IL_0082: ldloc.2
IL_0083: ldc.i4.4
IL_0084: ldstr " wild cats that like to visit"
IL_0089: stelem.ref
IL_008a: ldloc.2
IL_008b: call string [mscorlib]System.String::Concat(string[])
IL_0090: call void [mscorlib]System.Console::WriteLine(string)

Using String.Format:

Console.WriteLine(String.Format("Today is {0:M/d/yy} and I have {1} wild cats that like to visit", DateTime.Now, count));

Much much shorter than the other version:

IL_00ac: ldstr "Today is {0:M/d/yy} and I have {1} wild cats that like to visit"
IL_00b1: call valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now()
IL_00b6: box [mscorlib]System.DateTime
IL_00bb: ldloc.1
IL_00bc: box [mscorlib]System.Int32
IL_00c1: call string [mscorlib]System.String::Format(string, object, object)
IL_00c6: call void [mscorlib]System.Console::WriteLine(string)

The only thing that bothers me a little bit is the boxing operation, since String.Format only takes objects for the substitution parameters. From every other perspective, it is easier to read and results in more efficient IL.

\$\endgroup\$
  • \$\begingroup\$ Lots of helpful answers here, but I'm afraid I'm going to have to choose this answer as the best. @John thank you for checking the efficiency of the code and mentioning about readability. \$\endgroup\$ – block14 Nov 15 '13 at 6:31
  • \$\begingroup\$ @block14 don't be affraid, you did the right thing! \$\endgroup\$ – Mathieu Guindon Nov 28 '13 at 18:31
  • \$\begingroup\$ Interresting, but the number of lines of IL code isn't really a measure of efficiency or performance... \$\endgroup\$ – Guffa Feb 15 '14 at 15:23
11
\$\begingroup\$

For simple straightforward string concatenation like your "persian" cat example, I agree with @Malachi - being slightly memory-inefficient over a few bytes will not hurt anyone, and if there's a gain in readability then it's worth it.

However, since you're looking to standardize the way you're doing string concatenations, you should go with string.Format() all the way, without hesitation*.

Say you're writing a program for some petshop (say, one that only sells cats), you'll potentially want to format/concatenate the cat's breed, birthdate/age and price tag. With string + string your only option is essentially to hard-code the date and currency formats, while this:

string.Format("{0} cat, born {1:d} ({2} weeks) | {3:C2}", cat.Breed, 
                                                          cat.BirthDate, 
                                                          cat.AgeInWeeks, 
                                                          cat.Price)

...has an obvious advantage, especially when you consider that

"{0} cat, born {1:d} ({2} weeks) | {3:C2}"

is a string all by itself (as mentioned by Daniel Cook).

*At the end of the day, common sense should prevail. If you end up doing this:

var result = string.Format("{0}{1}", string1, string2);

...then you know you've gone overboard.

\$\endgroup\$
10
\$\begingroup\$

I much prefer method 1. Try adding 3-4 values and you'll quickly see it takes less code. Plus, you can reuse a value elsewhere in the string. Dunno which is more performant though. If you want performance, use StringBuilder.

\$\endgroup\$
  • 5
    \$\begingroup\$ Method 1 has the added bonus that you can save your format string to a variable allowing you to write something like Console.WriteLine(catSentence, myCat); \$\endgroup\$ – Daniel Nov 6 '13 at 21:34
  • 2
    \$\begingroup\$ Method 1 is better for localization: you can easily move the insertion point. \$\endgroup\$ – Alexey Ivanov Nov 7 '13 at 8:42
4
\$\begingroup\$

both methods have their uses,

I usually use the second method, for two reasons

  1. when I am writing the code I put in the variable that I want while writing instead of having to figure out which variable I needed when I am done writing it.

  2. it is faster to see what I am inserting into the string this way, I see the variable right away and know.

but I also Agree with Alex Dresko's Answer, and Daniel Cook's Comment to the answer (if that works I have not tried it myself, but I like the way it looks and works)

\$\endgroup\$
2
\$\begingroup\$

Well, first lets look at what the two options actually are. This is what the code is doing:

// Method 1 calls String.Format, which makes it functionally equivalent to:
Console.WriteLine(String.Format("I have a wild {0} cat that likes to visit", myCat));

// Method 2 is converted to this by the compiler:
Console.WriteLine(String.Concat("I have a wild ", myCat, " cat that likes to visit"));

If you are looking at performance, the second option is better, as it will simply concatenate the strings without having to parse a format string to determine what to do. However, the first option performs reasonably well, so in most applications the small performance difference will never be noticable.

If you are looking at readability, it's a matter of taste. A formatting string is easier to read, especially if you have several values, but it also makes it somewhat harder to see which value goes where in the string.

\$\endgroup\$
-1
\$\begingroup\$

Method 1 creates two strings in memory:

  1. "I have a wild {0} cat that likes to visit"
  2. "I have a wild persian cat that likes to visit"

Method 2 creates four strings in memory:

  1. "I have a wild "
  2. " cat that likes to visit"
  3. "I have a wild persian"
  4. "I have a wild persian cat that likes to visit"

Method 1 is better because it always allocates fewer strings. In this case it allocates half as many.

\$\endgroup\$
  • 2
    \$\begingroup\$ Method 2 should have "Persian" inserted before point 3. Also it would be good to note that method 1 has to parse the format which I believe is a small overhead. \$\endgroup\$ – James Khoury Nov 7 '13 at 3:19
  • \$\begingroup\$ can you post a reference link that this is what happens? I think this kind of answer should show proof. \$\endgroup\$ – Malachi Nov 7 '13 at 14:37
  • 1
    \$\begingroup\$ This is incorrect. Method 2 does not create an intermediate string from the two first strings. The compiler makes it a call to String.Concat which concatenates all the strings into one. \$\endgroup\$ – Guffa Nov 7 '13 at 14:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.