In a devBetter discussion, a question was raised regarding composing strings: “Why would a developer use String.Format, when string interpolation seems much cleaner and easier to use?”
In case you are not familiar with StringFormat or string interpolation, here’s a code sample that demonstrates using them to compose a string:
1 2 3 4 5 6 7 8 9 10 11 |
var name = "Rick"; var shoeSize = 9.5; // the manual approach var x = "My name is " + name + " and my shoe size is " + shoeSize.ToString(); // String.Format var y = String.Format("My name is {0} and my shoe size is {1}", name, shoeSize); // string interpolation var z = $"My name is {name} and my shoe size is {shoeSize}"; |
Variables x, y, and z will all contain the same string value (“My name is Rick and my shoe size is 9.5”), but you can see how each approach used becomes progressively easier to read and understand. String interpolation is like a shorthand for a call to String.Format.
We’ll soon see that behind the scenes, string interpolation actually compiles down into a call to String.Format, but let’s look at String.Format, first.
String.Format evaluates its format parameter at runtime. Consider the following code that accepts a format string from the console, and displays the result of String.Format():
1 2 3 4 5 6 7 8 9 10 |
var name="Rick"; var shoeSize=9; while(true) { Console.WriteLine("Enter a format that contains {0} and {1}: "); var format = Console.ReadLine(); Console.WriteLine("You entered: "+format); Console.WriteLine("Result: "+String.Format(format,name,shoeSize)); } |
Here’s a sample of the console output:
Enter a format that contains {0} and {1}:
You entered: Name {0} Shoe {1}
Result: Name Rick Shoe 9
Enter a format that contains {0} and {1}:
You entered: Shoe {1} Name {0}
Result: Shoe 9 Name Rick
In contrast, interpolation builds the format expression at compile time.
Let’s see what happens when we try to use string interpolation like String.Format:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var name = "Rick"; var shoeSize = 9.5; var format1="Name: {0}, Shoe: {1}"; var format2="Shoe: {1}, Name: {0}"; // Name: Rick, Shoe: 9.5 var result1 = String.Format(format1, name, shoeSize); // Shoe: 9.5, Name: Rick var result2 = String.Format(format2, name, shoeSize); var format3 = "Name: {name}, Shoe: {shoeSize}"; var interpolated = $format3; // this wont compile! // this displays the string "Name: {name}, Shoe: {shoeSize}" // without evaluating the embedded expressions var interpolated2 =$"{format3}"; |
Why don’t we get the same results? The reason is that the compiler “hard codes” the interpolated format.
Let’s look at how the compiler compiles the following string interpolation:
1 2 3 4 |
var name = "Rick"; var shoeSize = 9; var result = $"My name is {name} and my shoe size is {shoeSize}"; |
Here is the IL generated by the compiler.
1 2 3 4 5 6 7 8 9 10 11 12 |
IL_0000: nop IL_0001: ldstr "Rick" IL_0006: stloc.0 IL_0007: ldc.i4.s 09 IL_0009: stloc.1 IL_000A: ldstr "My name is {0} and my shoe size is {1}" IL_000F: ldloc.0 IL_0010: ldloc.1 IL_0011: box System.Int32 IL_0016: call System.String.Format IL_001B: stloc.2 IL_001C: ret |
Compared to our “changeable” format in the while(true) loop, the format expression (the line starting with IL_000A) is “baked in”: the string “My name is …” will never change.
Both String.Format and string interpolation are valuable tools in our toolbelt. I use string interpolation most of the time (since it was added to C# in version 6), but String.Format can be very useful when you need to have configurable formats.