First let's get to know both types.
The type string
is a reference type inside of the .NET Framework. So it "lives" only in memory heap , a storage memory of simple objects and quick access.
But its main characteristic is the type string
is a unchanging object , that is, when we define a new value to a string
type, which happens is that the old value is completely destroyed, and in a new memory location the new value is stored, then the string
object refers to this new position in memory.
See the image below for the behavior.
Source: link
This "re-creation" of the string at each concatenation is, in a sense, costly
The type StringBuilder
, is already a complex type, represents a changeable type of string, that is, a changeable type of string
.
It works as follows: Each time a new string
is added, it breaks this string
into char
s and stores it one by one at an internal char[]
. View your source code here .
The advantage is that it ensures that a value will never be "recreated," as it does with string
type. Each time a new value is added, it just inserted into your stack.
This, of course, comes with other advantages. With StringBuilder
you can remove a single char
from the middle of the string, just as you can also insert a char
in the middle of the string. Both only specifying position by index.
When to use each?
If the StringBuilder is more performative than string
, then should I always use StringBuilder
for concatenation?
Of course not. It should be used when it really is worth it. It is important not to forget that StringBuilder
is a complex type, it must be instantiated. However it is more expensive to boot.
In the case of a simple concatenation like the example below, it would not be worth using StringBuilder
:
var nome = "Thiago";
var sobrenome "Lunardi";
var nomeCompleto = nome +" "+ sobrenome;
nomeCompleto;
> "Thiago Lunardi"
Because it is a short execution - just a concatenation - making only the use of the string
type more efficient.
But, 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, with each 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(',');
Is there any way to improve further?
Yes, there is, not much more, but depending on the scenario, the improvement is significant.
No StringBuilder
, every time a new value is added, it needs expand your storage . This requires even a minimum processing time for allocation of the new block.
To speed up, you can initialize the StringBuilder
already with the expected size, so when adding new values, you will not spend time expanding your 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 in .NET Fiddle to do a performance test String vs StringBuilder .
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 types string
and StringBuilder
, concatenate a char
several times and measure how many ticks were required for each operation.
One of the results was as follows:
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
Note that, concatenating a few times, string
is faster than StringBuilder
, but with increasing number of concatenations, StringBuilder
becomes much more performative.