This is one topic that keeps popping up every now and then. A lot of people seem to be curious about the performance implications of using one versus the other, and not unexpectedly, a lot of different answers come up.
To settle it once for all, here’s a small program that uses both of them.
static void Main()
static void Method1()
string s = "";
static void Method2()
string s = string.Empty;
static void DoNothing(string s)
As you can see above, Method1 and Method2 call DoNothing, passing “” and string.Empty respectively. Now how do we compare these two? Easy, just look at the jitted code – after all, that’s what is going to run on the machine.
We’ll use Windbg to disassemble the code after the JITter did its job. And if you’re wondering why Method1 and Method2 have been called twice, now you know why – the second time around, we can look at the JITted code that was generated as part of the first time execution of each of those methods.
So breaking into the debugger at Console.ReadLine() and dumping the method descriptors for Program
0:003> !Name2EE Sample.exe!ConsoleApplication.Program
0:003> !DumpMT -MD 002a3048
Entry MethodDesc JIT Name
00860070 002a3020 JIT ConsoleApplication.Program.Main()
008600a8 002a3028 JIT ConsoleApplication.Program.Method1()
00860100 002a3030 JIT ConsoleApplication.Program.Method2()
008600c8 002a3038 JIT ConsoleApplication.Program.DoNothing(System.String)
002ac021 002a3040 NONE ConsoleApplication.Program..ctor()
shows the method descriptors for all the methods in that class. Now, using !u to disassemble code for Method1
0:003> !u 002a3028
Normal JIT generated code
Begin 008600a8, size d
008600a8 8b0d3c30e402 mov ecx,dword ptr ds:[2E4303Ch] ("")
008600ae ff158c302a00 call dword ptr ds:[2A308Ch] (ConsoleApplication.Program.DoNothing(System.String), mdToken: 06000004)
008600b4 c3 ret
and then for Method2
0:003> !u 002a3030
Normal JIT generated code
Begin 00860100, size c
00860100 8b0d2c10e402 mov ecx,dword ptr ds:[2E4102Ch] ("")
00860106 e8bdffffff call 008600c8 (ConsoleApplication.Program.DoNothing(System.String), mdToken: 06000004)
0086010b c3 ret
shows that, surprise surprise, the generated assembly code is identical. The only difference is the address referenced in the mov instruction.
So there you have it – the JITter generates the same code whether you use String.Empty or “”, and there should no difference in performance.
PS : It’s interesting to note that the two addresses are different, even though the string contents are the same. Address 2E4303Ch in the disassembly for Method1 is the address referring to an interned string object with the content “”. You can verify that by creating another string variable initialized to “” and disassembling that code – the same address (2E4303Ch) will be referenced there as well. What’s surprising is that the address referenced in Method2 is different – which means the string object referenced by string.Empty is not the interned object. Wonder why – maybe because it’s ngen’ned code?