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 initially).
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 faster in CPU time than format, easiest on cpu is interp and concat, however the memory is nearing 1MB.
Appending to the var
When constructing a string over successive lines (appending separately in the builtbylines tests as you may for error code messages) String format slips behind the others when using += to append to the output var. Stringbuilder in this instance is the clear winner.
Multiple runs of the functions
Here we can see the difference in a very simple 20x run of the line concat that could be found in a function if you wanted to track progress or the part of the function you are attempting to do. The difference between using a builder vs a string is nearly 25%. If you had even a small amount of strings assigned inside a loop of lots of records then the potential memory impact could be quite high by using interp/+=.
For example if I were importing a relatively small file of records into a database and was using strings then the number of records could easily exceed 50000 in a very short period of time (let alone the 4gb compressed files I used to have to import), which means the system as a whole could easily crash or end up very slow as it is forced the GC repeatedly within a very short period of time. In those cases I would se a stringbuilder ref and simply refresh and re-assign that OR use a Span.
Source code
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnostics.Windows.Configs;
using BenchmarkDotNet.Running;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
[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}", str1, str2, str3, str4);
}
[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}", str1, str2, str3, str4);
output = String.Format("{0} {1} {2} {3}", str1, str2, str3, str4);
output = String.Format("{0} {1} {2} {3}", str1, str2, str3, str4);
output = String.Format("{0} {1} {2} {3}", str1, str2, str3, str4);
}
[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);
}
[Benchmark]
public void TestConcatLine20x()
{
for (int i = 0; i < 20; i++) {
TestStringConcat_BuiltByLine();
}
}
[Benchmark]
public void TestInterpLine20x()
{
for (int i = 0; i < 20; i++)
{
TestStringInterp_BuiltByLine2();
}
}
[Benchmark]
public void TestBuilderLine20x()
{
for (int i = 0; i < 20; i++)
{
TestStringBuilder_BuiltByLine();
}
}
static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<Program>(null, args);
//var summary = BenchmarkRunner.Run())
}
}
}