When to use String and StringBuilder [duplicate]

3

There is another question about What is the most appropriate way to concatenate strings? . It explains the possible types of concatenations in C# .

But when should we use System.String and when should we use System.Text.StringBuilder ? What's the difference between the two? And, what better, when does not use?

    
asked by anonymous 29.08.2017 / 10:58

1 answer

6

First let's get to know both types.

System.String

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

System.Text.StringBuilder

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.

    
29.08.2017 / 10:58