'Memory usage of concatenating strings using interpolated vs "+" operator

I see the benefit of using interpolated strings, in terms of readability:

string myString = $"Hello { person.FirstName } { person.LastName }!"

over a concatenation done this way:

string myString = "Hello " + person.FirstName + " " person.LastName + "!";

The author of this video tutorial claims that the first one makes better use of memory.

How come?



Solution 1:[1]

The author doesn't actually say that one makes better use of memory than the other. It says that the one method "makes good use of memory" in the abstract, which, by itself, doesn't really mean much of anything.

But regardless of what they said, the two methods aren't going to be meaningfully different in their implementation. Neither is going to be meaningfully different from the other in terms of memory or time.

Solution 2:[2]

I made a simple test, see below. If you concatenate constants, don't use "string.Concat" because the compiler can't concatenate your strings at compile time. If you use variables, the results are effectively the same.

time measure results:

const string interpolation : 4
const string concatenation : 58
const string addition      : 3
var string interpolation   : 53
var string concatenation   : 55
var string addition        : 55
mixed string interpolation : 47
mixed string concatenation : 53
mixed string addition      : 42

the code:

void Main()
{

const int repetitions = 1000000; 
const string part1 = "Part 1"; 
const string part2 = "Part 2"; 
const string part3 = "Part 3"; 
var vPart1 = GetPart(1); 
var vPart2 = GetPart(2); 
var vPart3 = GetPart(3); 

Test("const string interpolation ", () => $"{part1}{part2}{part3}"); 
Test("const string concatenation ", () => string.Concat(part1, part2, part3)); 
Test("const string addition      ", () => part1 + part2 + part3); 
Test("var string interpolation   ", () => $"{vPart1}{vPart2}{vPart3}"); 
Test("var string concatenation   ", () => string.Concat(vPart1, vPart2, vPart3)); 
Test("var string addition        ", () => vPart1 + vPart2 + vPart3); 
Test("mixed string interpolation ", () => $"{vPart1}{part2}{part3}");
Test("mixed string concatenation ", () => string.Concat(vPart1, part2, part3));
Test("mixed string addition      ", () => vPart1 + part2 + part3);

void Test(string info, Func<string> action) 
{ 
    var watch = Stopwatch.StartNew(); 
    for (var i = 0; i < repetitions; i++) 
    { 
        action(); 
    } 
    watch.Stop(); 
    Trace.WriteLine($"{info}: {watch.ElapsedMilliseconds}"); 
} 

string GetPart(int index) 
    => $"Part{index}"; 

}

Solution 3:[3]

Strings are immutable. That means they can't be changed.

When you concatenate strings with a + sign, you end up creating multiple strings to get to the final string.

When you use the interpolation method (or StringBuilder), the .NET runtime optimizes your string use, so it (in theory) has better memory usage.

All that being said, it often depends on WHAT you are doing, and HOW OFTEN you are doing it.

One set of concatenations doesn't offer a lot of performance/memory improvements.

Doing those concatenations in a loop can have a lot of improvement.

Solution 4:[4]

Because strings in c# are immutable that's why same memory is used again and again so it does not impact memory much but in terms of performance you are actually differentiating between String.Format and String.Concat because at compile time your code will be like this

  string a = "abc";
  string b = "def";

  string.Format("Hello {0} {1}!", a, b);

  string.Concat(new string[] { "Hello ", a, " ", b, "!" });

there is a whole thread about performance between these two if you are interested String output: format or concat in C#

Solution 5:[5]

I created a memory test program, I had a bug in one of the benchmarks earlier on so I have fixed that and I have posted the source below the results. A note, this is using C# 7 if you use .,net core you will be using a different version of C# and these results will change.

Further to the immutable arguments above, the allocation is at the point of assignation. so the var output = "something"+"something else"+" "+"something other" contains 2 assignations, the variable assign on the left and the final string on the right (as it is optimised this way by the compiler when a fixed number of vars is used).

As shown below these assignations happen every time you use this method (string.format and stringbuilder differ here, format uses less memory and builder has extra overhead).

Simple

So if you are simply adding vars into a single string yes Interp and Inline Concat use the same amount of RAM, string.format uses the least RAM though so there is obviously some extra allocations occurring with concat & interp that string format avoids.

Using the 1 var and assigning to it multiple times

Interestingly, in the multiline assigns (where you assign the same value to the var multiple times) even with 3 clears and appendformats added to the stringbuilder it is the most efficient in the multiline assigns and is still 2.5x faster in CPU time than format.

Appending to the var

When constructing a string over successive lines (appending separately in the builtbylines tests) String format slips behind the others when using += to append to the output var. Stringbuilder in this instance is the clear winner.

Results

Here is the source:

[AsciiDocExporter]
[MemoryDiagnoser]
public class Program
{
    private string str1 = "test string";
    private string str2 = "this is another string";
    private string str3 = "helo string 3";
    private string str4 = "a fourth string";
            
    [Benchmark]
    public void TestStringConcatStringsConst()
    {
        var output = str1 + " " + str2 + " " + str3 + " " + str4;
    }


    [Benchmark]
    public void TestStringInterp()
    {
        var output = $"{str1} {str2} {str3} {str4}";
    }

    [Benchmark]
    public void TestStringFormat()
    {            
        var output = String.Format("{0} {1} {2} {3}", 1, 2, 3, 4);
    }

    [Benchmark]
    public void TestStringBuilder()
    {
        var output = new StringBuilder().AppendFormat("{0} {1} {2} {3}", str1, str2, str3, str4);
    }

    [Benchmark]
    public void TestStringConcatStrings_FourMultiLineAssigns()
    {
        var output = str1 + " " + str2 + " " + str3 + " " + str4;
        output = str1 + " " + str2 + " " + str3 + " " + str4;
        output = str1 + " " + str2 + " " + str3 + " " + str4;
        output = str1 + " " + str2 + " " + str3 + " " + str4;
    }

    [Benchmark]
    public void TestStringInterp_FourMultiLineAssigns()
    {
        var output = $"{str1} {str2} {str3} {str4}";
        output = $"{str1} {str2} {str3} {str4}";
        output = $"{str1} {str2} {str3} {str4}";
        output = $"{str1} {str2} {str3} {str4}";
    }

    [Benchmark]
    public void TestStringFormat_FourMultiLineAssigns()
    {
        var output = String.Format("{0} {1} {2} {3}", 1, 2, 3, 4);
        output = String.Format("{0} {1} {2} {3}", 1, 2, 3, 4);
        output = String.Format("{0} {1} {2} {3}", 1, 2, 3, 4);
        output = String.Format("{0} {1} {2} {3}", 1, 2, 3, 4);
    }

    [Benchmark]
    //This also clears and re-assigns the data, I used the stringbuilder until the last line as if you are doing multiple assigns with stringbuilder you do not pull out a string until you need it.
    public void TestStringBuilder_FourMultilineAssigns()
    {
        var output = new StringBuilder().AppendFormat("{0} {1} {2} {3}", str1, str2, str3, str4);
        output = output.Clear().AppendFormat("{0} {1} {2} {3}", str1, str2, str3, str4);
        output = output.Clear().AppendFormat("{0} {1} {2} {3}", str1, str2, str3, str4);
        output = output.Clear().AppendFormat("{0} {1} {2} {3}", str1, str2, str3, str4);
    }

    [Benchmark]
    public void TestStringConcat_BuiltByLine()
    {
        var output = str1;
        output += " " + str2;
        output += " " + str3;
        output += " " + str4;
    }

    [Benchmark]
    public void TestStringInterp_BuiltByLine1()
    {
        var output = str1;
        output = $"{output} {str2}";
        output = $"{output} {str3}";
        output = $"{output} {str4}";
    }

    [Benchmark]
    public void TestStringInterp_BuiltByLine2()
    {
        var output = str1;
        output += $" {str2}";
        output += $" {str3}";
        output += $" {str4}";
    }

    [Benchmark]
    public void TestStringFormat_BuiltByLine1()
    {
        var output = str1;
        output = String.Format("{0} {1}", output, str2);
        output = String.Format("{0} {1}", output, str3);
        output = String.Format("{0} {1}", output, str4);
    }

    [Benchmark]
    public void TestStringFormat_BuiltByLine2()
    {
        var output = str1;
        output += String.Format(" {0}", str2);
        output += String.Format(" {0}", str3);
        output += String.Format(" {0}", str4);
    }

    [Benchmark]
    public void TestStringBuilder_BuiltByLine()
    {
        var output = new StringBuilder(str1);
        output.AppendFormat("{0}", str2);
        output.AppendFormat("{0}", str3);
        output.AppendFormat("{0}", str4);
    }

    static void Main(string[] args)
    {
        var summary = BenchmarkRunner.Run<Program>(null, args);
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Servy
Solution 2
Solution 3 Eric Burdo
Solution 4 Community
Solution 5 Slipoch