First let’s meet the two guys.
The guy string
is a reference type within the . NET Framework. Therefore it "lives" only in memory heap, a simple and quick-access object storage memory.
But its main feature is the type string
is a immutable object, that is, when we define a new value to a type string
, what happens is that the old value is completely destroyed, and in a new memory position is stored the new value, then the object string
now refers to that new position in the memory.
See in the image below what is this behavior.
Source: https://passionatetalks.wordpress.com/2014/07/20/why-strings-are-immutable-in-dot-net/
This "recreation" of string every concatenation is, in a way, costly
The guy StringBuilder
, is already a complex type, represents a mutable type of string, ie a mutable type of string
.
It works like this: Every time a new string
is added, it breaks this string
in char
s and stores one by one on one char[]
intern. See your source code here.
The advantage said is that it ensures that a value will never be "recreated", as with the type string
. Each time a new value is add, it just inserted into your stack.
This, of course, comes with other advantages. With StringBuilder
it is possible to remove a single char
of the middle of the chain, as well as it is possible to insert a char
in the middle of the string. Both only specifying the position per index.
When to use each?
If Stringbuilder is more performative than string
, then I must always wear StringBuilder
for concatenation?
Of course not. It should be used when really worth it. Important not to forget that the StringBuilder
is a complex type, must be instantiated. Poranto is more expensive to be initialized.
In the case of a simple concatenation as the example below, it would not be worth using StringBuilder
:
var nome = "Thiago";
var sobrenome "Lunardi";
var nomeCompleto = nome +" "+ sobrenome;
nomeCompleto;
> "Thiago Lunardi"
For it is a short execution - only a concatenation - making, in this case, only the use of the type string
more efficient.
But, already in the case below, it would be worth a refactoring:
var nomesDosAlunos = string.Empty;
foreach(var aluno in salaDeAula.Alunos)
nomesDosAlunos += aluno.Nome + ',';
After all, in this case, every loop, a new string
is rebuilt. This would be a good opportunity to gain performance with StringBuilder
.
var nomesDosAlunos = new StringBuilder();
foreach(var aluno in salaDeAula.Alunos)
nomesDosAlunos.Append(aluno.Nome).Append(',');
There’s room for improvement?
Yes, there is, not much else, but depending on the scenario, the improvement is significant.
In the StringBuilder
, each time a new value is added, it needs expand your storage capacity. This requires, even if minimal, a processing time for allocation of the new block.
To speed up, you can initialize the StringBuilder
already with the expected size, thus, when adding new values, it will not consume time expanding its blocks.
var nomesDosAlunos = new StringBuilder(salaDeAula.Alunos.Count);
foreach(var aluno in salaDeAula.Alunos)
nomesDosAlunos.Append(aluno.Nome).Append(',');
Performance test
I wrote a script on . NET Fiddle to make a String vs Stringbuilder performance test.
public class Program
{
private static Stopwatch _watch = new Stopwatch();
public static void Main()
{
for(var times = 1; times <= 1000; times *= 10)
{
var stringTestResult = Test(() => StringTest(times));
var stringBuilderTestResult = Test(() => StringBuilderTest(times));
var stringBuilderPresetTestResult = Test(() => StringBuilderPresetTest(times));
if(times < 1) {times=1;continue;} // first time is warming up, doesn't count
Console.WriteLine($"Testing against {times} times concatenation.");
Console.WriteLine($"String: {stringTestResult}");
Console.WriteLine($"StringBuilder: {stringBuilderTestResult}");
Console.WriteLine($"StringBuilderPreset: {stringBuilderPresetTestResult}");
Console.WriteLine();
}
}
public static long Test(Action test)
{
_watch.Restart();
for(var x =0; x<=100; x++) test();
_watch.Stop();
var ticks = _watch.ElapsedTicks;
return ticks;
}
public static void StringTest(int times)
{
var s = string.Empty;
for(var x = 0; x < times; x++)
s += ' ';
}
public static void StringBuilderTest(int times)
{
var s = new StringBuilder();
for(var x = 0; x < times; x++)
s.Append(' ');
}
public static void StringBuilderPresetTest(int times)
{
var s = new StringBuilder(times);
for(var x = 0; x < times; x++)
s.Append(' ');
}
}
The idea is, for guys string
and StringBuilder
, concatenate several times a char
and measure how many ticks were necessary for each operation.
One of the results was the following:
Testing against 1 times concatenation.
String: 17
StringBuilder: 33
StringBuilderPreset: 11
Testing against 10 times concatenation.
String: 37
StringBuilder: 29
StringBuilderPreset: 15
Testing against 100 times concatenation.
String: 584
StringBuilder: 80
StringBuilderPreset: 56
Testing against 1000 times concatenation.
String: 89935
StringBuilder: 658
StringBuilderPreset: 425
Notice that, concatenating a few times, the string
is faster than the StringBuilder
, however, as the number of concatenations increases, StringBuilder
becomes much more performatic.
I don’t understand that it’s the same question. In referenced, one asks the types of concreteness, here I ask when to use concatenation and when to use Stringbuilder.
– Thiago Lunardi