20 min read

Mastering String Concatenation in C#: Methods, Pros, and Cons

Every developer has to deal with string concatenation at some time because it is a basic operation in every language. Understanding the many string concatenation algorithms and their trade-offs is essential whether you're developing a web application, a script, or a game.
In this blog post, we'll examine the most popular approaches to string concatenation in C#, offer code samples, and go over each approach's advantages and disadvantages. We'll concentrate on techniques for joining a variable number of strings of different lengths.

Initial Test Configuration

The following basic parameters will be used for the overall methods comparison:

// our array of strings that we need to concatenate
private List<string> strings = new List<string>();
// We wil run the the test for 4 diff array lengths: 10 & 100 & 1000 & 10000
//[Params(10, 100, 1000, 10000)]
public int NumberOfStrings { get; set; }

// We wil run the the test for 2 diff string lengths: 10 & 100
[Params(10, 100)]
public int StringLength { get; set; }

Initial Parameters

The code below will be used to generate the random strings as well as the initial setup:

public static string RandomString(int length)
{
    const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    return new string(Enumerable.Repeat(chars, length)
        .Select(s => s[random.Next(s.Length)]).ToArray());
}

public void Setup()
{
    for(int i = 0; i < NumberOfStrings; i++) { 
        strings.Add(RandomString(StringLength));
    }            
}

Having stated that, let's look at the techniques we are evaluating today.

The Basics: Using the + Operator

The + operator in C# is the easiest way to join strings. It's a popular choice for compact concatenations due to its simplicity and straightforwardness.

Example:

public string PlusOperator()
{
    var result = string.Empty;
    for (int i = 0; i < NumberOfStrings; i++)
    {
        result = result + strings[i];
    }
    return result;
}

Pros:

  • Easy to use and comprehend.
  • Appropriate for basic concatenation operations.
  • Works nicely when only a few string concatenations are involved.

Cons:

  • Because a new string object is created each time, it is inefficient for repeated concatenation operations.

Benchmarks

We utilized the following parameters to examine how this method scales as the number and length of the strings increase:

[Params(10, 100, 1000, 10000, 100000)]
public int NumberOfStrings { get; set; }

[Params(10, 100)]
public int StringLength { get; set; }

+ Operator Benchmark parameters

After an excruciating wait amount (01:43:37) we get the following results:

# Strings String Length Mean Error StdDev Allocated
10 10 121.9 ns 1.90 ns 1.69 ns 1.28 KB
10 100 526.9 ns 9.85 ns 20.35 ns 10.76 KB
100 10 4,877.5 ns 95.51 ns 164.75 ns 101.13 KB
100 100 39,929.5 ns 781.16 ns 1,347.46 ns 988.45 KB
1000 10 409,115.8 ns 8,177.66 ns 20,365.22 ns 9800.73 KB
1000 100 8,728,488.6 ns 142,505.51 ns 118,998.55 ns 97785.5 KB
10000 10 101,500,933.6 ns 1,982,547.69 ns 2,434,746.60 ns 976997.65 KB
10000 100 2,913,516,093.8 ns 55,183,820.69 ns 54,197,890.65 ns 9767232.68 KB
100000 10 34,432,751,564.3 ns 221,287,906.99 ns 196,165,965.67 ns 97660877.27 KB
100000 100 221,595,051,628.6 ns 1,408,263,963.50 ns 1,248,389,322.65 ns 976578167.18 KB
Graph is in Log 10 scale

We would have expected the time and memory consumption to be increasing in a linear fashion however as you can see from the graph and the table above the increase is logarithmic. If for 10 strings each having 100 characters in length we used 10.76KB and were able to run the concatenation in 526.9ns when we get to bigger strings and more of them we see for 1000 strings of 100 characters and increase to 97.7 MB and 8.7 milliseconds.

Going even further to 100,000 strings of 100 characters we get to insane amounts of 3.7 minutes and 976.6GB. While this scenario is unlikely it is a good way to demonstrate that even at first glance it is a very good method it does not scale well with size.

That being said I still think that this method is still the one to use if you need a quick way to add a couple of strings together.


Efficiency and Performance: using StringBuilder

When you need to perform many string concatenations, especially in a loop or for complex operations, StringBuilder is a more efficient choice. It minimizes memory overhead by modifying the existing string buffer.

Example:

public string StringBuilderMethod()
{
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < NumberOfStrings; i++)
    {
        sb.Append(strings[i]);
    }
    return sb.ToString();
}

Pros:

  • Efficient for large-scale or repeated concatenation operations.
  • Reduces memory usage because it modifies an existing buffer.
  • Provides better performance compared to the + operator for extensive concatenation.

Cons:

  • Slightly more verbose than the + operator for simple concatenations.

Benchmarks

We are using the same test parameters as in the + Operator part.

# Strings String Length Mean Error StdDev Allocated
10 10 115.7 ns 1.91 ns 1.69 ns 768 B
10 100 311.8 ns 6.21 ns 9.30 ns 5656 B
100 10 702.3 ns 10.24 ns 7.99 ns 4576 B
100 100 2,279.4 ns 45.29 ns 74.42 ns 46272 B
1000 10 5,814.9 ns 95.14 ns 89.00 ns 53200 B
1000 100 105,793.9 ns 1,694.21 ns 1,501.87 ns 403085 B
10000 10 151,350.0 ns 1,431.72 ns 1,339.23 ns 410013 B
10000 100 1,203,541.6 ns 24,496.60 ns 72,228.77 ns 4019591 B
100000 10 1,907,386.3 ns 36,858.46 ns 39,438.14 ns 4011056 B
100000 100 27,886,235.4 ns 546,256.10 ns 765,774.76 ns 40108408 B
Graph is in Log 10 scale

Again we see that the memory and time increase is not in a linear fashion but the performance is greatly improved over the + operator. For the same conditions (100,000 string of 100 characters each) we have a time of 27 milliseconds (as opposed to 221.59 seconds) and a memory allocation of 40MB (as opposed to 976.6 GB)


StringBuilder Alternative

One alternative to the Append method StringBuilder method above is using AppendJoin. This can only be used if we have all the strings we want to add in a collection (array/list etc.) already.

Example:

public string StringBuilderJoinMethod()
{
    StringBuilder sb = new StringBuilder();
    sb.AppendJoin("", strings);            
    return sb.ToString();
}

It has all the Pros and Cons of the initial StringBuilder approach with the condition that the string to add are already in an array. While this could work very nice with collections that are retrieved from a database I do not think it is practical to create an array just for 2-3 values (for example preparing a log entry where we would have to join a message, an error code, and a description since we already have the strings).

Benchmarks

# Strings String Length Mean Error StdDev Allocated
10 10 222.6 ns 4.42 ns 4.14 ns 808 B
10 100 401.3 ns 7.89 ns 9.98 ns 5696 B
100 10 1,589.5 ns 27.32 ns 26.84 ns 4616 B
100 100 2,852.6 ns 49.98 ns 71.68 ns 46312 B
1000 10 14,358.2 ns 216.63 ns 192.04 ns 53240 B
1000 100 112,916.9 ns 2,153.84 ns 2,304.59 ns 403125 B
10000 10 228,084.0 ns 1,721.81 ns 1,610.59 ns 410053 B
10000 100 1,023,579.2 ns 19,992.55 ns 37,550.85 ns 4019695 B
100000 10 1,876,677.7 ns 37,503.34 ns 60,560.91 ns 4011143 B
100000 100 27,236,426.6 ns 544,572.27 ns 847,833.42 ns 40108448 B
Log 10 scale

We can see that the performance and memory usage is all but similar to the previous StringBuilder method so this is just an approach in a specific case.


String Interpolation: The Readable Approach

By utilizing curly brackets {} to embed expressions into a string, string interpolation is a clear and legible approach to concatenate strings.

Example:

public string PrepareLogEntry(string message, 
MessageType messageType, int code, string description)
{
    return $"At {DateTime.Now} we got the {GetMessageTypeAsText(messageType)}{Environment.NewLine}{message}:::{code*2}:::{description}";
}

As you can see in the example above we used strings, methods and expressions in a string interpolation.

Pros:

  • Highly readable and maintainable.
  • Simplifies the process of embedding variables and expressions within a string.

Cons:

  • May be less efficient than StringBuilder for extensive concatenation, but still more efficient than the + operator.
  • Need to be escape (by doubling) the special { and } characters
  • Can not be used on a variable number of elements. You need to account for the strings to add beforehand.

Benchmarks

For the test we used the following premises:

  • 10 strings of length 10, 100, 1,000, 10,000, 100,000
  • baseline was the + operator
  • we ran String.Concat, String.Format benchmarks as well besides the interpolation and the + operator - more on them in the tests below
Method Mean Error StdDev Median Ratio RatioSD Rank Allocated Alloc Ratio
String Length: 10
+ 64.73 ns 0.881 ns 0.905 ns 64.50 ns 1.00 0.00 2 328 B 1.00
$"{}" 62.73 ns 1.292 ns 1.269 ns 62.76 ns 0.97 0.02 1 224 B 0.68
Concat 62.21 ns 1.138 ns 1.064 ns 62.44 ns 0.96 0.02 1 328 B 1.00
Format 193.84 ns 3.519 ns 3.119 ns 193.09 ns 2.99 0.06 3 328 B 1.00
String Length: 100
+ 111.83 ns 2.824 ns 8.327 ns 108.03 ns 1.00 0.00 1 2128 B 1.00
$"{}" 218.73 ns 3.776 ns 3.347 ns 219.16 ns 1.81 0.13 3 2024 B 0.95
Concat 118.61 ns 2.586 ns 7.504 ns 117.77 ns 1.06 0.06 2 2128 B 1.00
Format 349.67 ns 5.865 ns 5.486 ns 349.47 ns 2.89 0.17 4 2128 B 1.00
String Length: 1,000
+ 781.20 ns 15.569 ns 45.662 ns 770.00 ns 1.00 0.00 1 20128 B 1.00
$"{}" 5,631.26 ns 110.630 ns 135.864 ns 5,596.50 ns 7.39 0.46 3 20024 B 0.99
Concat 803.31 ns 16.063 ns 40.299 ns 797.60 ns 1.04 0.07 2 20128 B 1.00
Format 5,810.15 ns 108.289 ns 115.868 ns 5,780.55 ns 7.63 0.45 4 20128 B 1.00
String Length: 10,000
+ 82,568.40 ns 1,625.047 ns 2,113.020 ns 82,420.86 ns 1.00 0.00 1 200149 B 1.00
$"{}" 95,776.44 ns 1,121.619 ns 1,049.163 ns 95,254.87 ns 1.16 0.04 2 200045 B 1.00
Concat 84,254.60 ns 1,673.430 ns 1,643.532 ns 84,287.26 ns 1.02 0.04 1 200149 B 1.00
Format 99,216.05 ns 1,627.464 ns 1,442.704 ns 98,921.45 ns 1.21 0.04 3 200149 B 1.00
String Length: 100,000
+ 377,759.31 ns 7,491.738 ns 7,357.888 ns 376,888.43 ns 1.00 0.00 1 2000232 B 1.00
$"{}" 424,039.40 ns 8,331.667 ns 7,793.446 ns 424,410.64 ns 1.12 0.04 3 2000102 B 1.00
Concat 382,876.49 ns 5,400.808 ns 4,509.919 ns 384,043.65 ns 1.02 0.02 1 2000236 B 1.00
Format 413,956.44 ns 4,851.527 ns 4,538.121 ns 412,556.15 ns 1.10 0.02 2 2000347 B 1.00
Log 10 scale

Linear scale

Looking at the results (and the 2 graphics above) we can see that the string interpolation is a bit better than the + operator when using small strings but as the string size increases the performance decreases. This coupled with the high readability of it makes it a good option for concatenation when using with small strings or a small number of strings.

You may have also seen that we grouped String.Format here. While the method has a lot on use-cases in terms of memory and speed it is not the best out there.


String.Concat and String.Join: Memory-Friendly Options

By using less resources and preventing the formation of additional string objects, the String.Concat and String.Join methods make it possible to concatenate many strings effectively.

Example:

public string StringJoinMethod()
{
    return String.Join("", strings);
}

public string StringConcatMethod()
{
    return string.Concat(strings);
}

Pros:

  • Efficient and memory-friendly for concatenating multiple strings.
  • Provides a concise way to concatenate strings.

Cons:

  • Slightly less intuitive and more verbose compared to other methods for simple concatenations.

Benchmarks

We are back to the original testing parameters (10, 100,..., 100,000 strings of 10/100 characters)

# Strings String Length Mean Error StdDev Allocated
Join
10 10 71.82 ns 1.362 ns 1.569 ns 224 B
10 100 138.47 ns 2.810 ns 5.548 ns 2024 B
100 10 712.11 ns 13.488 ns 15.533 ns 2024 B
100 100 1,327.22 ns 26.586 ns 66.699 ns 20024 B
1000 10 6,952.19 ns 132.831 ns 124.250 ns 20024 B
1000 100 82,692.78 ns 1,621.561 ns 1,516.809 ns 200045 B
10000 10 141,403.33 ns 1,871.830 ns 1,750.911 ns 200045 B
10000 100 449,650.15 ns 7,860.112 ns 6,967.785 ns 2000102 B
100000 10 913,902.50 ns 8,593.624 ns 7,618.024 ns 2000091 B
100000 100 6,929,308.96 ns 124,949.937 ns 116,878.249 ns 20000161 B
Concat
10 10 115.58 ns 2.295 ns 2.357 ns 264 B
10 100 264.78 ns 5.297 ns 10.078 ns 2064 B
100 10 1,073.04 ns 21.370 ns 29.251 ns 2064 B
100 100 6,387.12 ns 113.094 ns 105.788 ns 20064 B
1000 10 14,019.86 ns 207.155 ns 193.773 ns 20064 B
1000 100 105,150.19 ns 1,676.485 ns 1,568.185 ns 200085 B
10000 10 183,683.38 ns 2,569.525 ns 2,403.535 ns 200085 B
10000 100 625,732.25 ns 5,523.159 ns 5,166.366 ns 2001236 B
100000 10 1,192,823.37 ns 14,475.475 ns 13,540.368 ns 2001047 B
100000 100 9,781,831.75 ns 195,450.444 ns 456,859.112 ns 20001636 B
Log 10 scale

If we compare the results from both methods to the original + operator we see a huge increase in performance (both smaller time and memory size).

Just a reminder that the original + operator for 10,000 arrays of 100 length took 2.9 seconds and a memory of 9.7 GB and using any of the String.Concat or String.Join we get 0.4 ms (or 0.6ms) and about 2 MB of memory.

If we look at the more extreme case we tested (100,000 arrays with 100 length) we get the following:

Method Time Memory
+ 221.6 s 976.6 GB
Concat 9.8 ms 20 MB
Join 6.9 ms 20 MB

While String.Join performs slightly faster than String.Concat in a day to day use case they will be pretty close.


Span<T>: The Memory-Efficient Powerhouse

Span is a great C# 7.2 feature which brings together memory efficiency and performance. When it comes to string concatenation, this is a game changer.

Example:

public string SpanMethod()
{
    var length = 0;
    for (int i = 0; i < NumberOfStrings; i++)
    {
        length += strings[i].Length;
    }
    // Allocate space on the stack
    Span<char> combinedSpan = stackalloc char[length]; 

    length = 0;
    for (int i = 0; i < NumberOfStrings; i++)
    {
        strings[i].AsSpan().CopyTo(combinedSpan.Slice(length));
        length += strings[i].Length;
    }

    // Create a new string from the combinedSpan
    return new string(combinedSpan); 
}

Pros:

  • Excellent memory efficiency by minimizing intermediate string objects.
  • Good execution speed, especially for moderately sized strings.

Cons:

  • May run into stack space limitations for very large strings. This is seen in our benchmarks as we can't get results after the 10,000 elements with 100 size

Benchmarks

If we look at the results we can clearly see the stack limitation.

# Strings String Length Mean Error StdDev Allocated
10 10 56.79 ns 1.154 ns 1.830 ns 224 B
10 100 181.87 ns 3.682 ns 9.505 ns 2024 B
100 10 501.36 ns 8.935 ns 10.289 ns 2024 B
100 100 1,948.42 ns 38.507 ns 64.337 ns 20024 B
1000 10 5,163.55 ns 47.698 ns 44.617 ns 20024 B
1000 100 94,934.28 ns 1,892.397 ns 2,252.764 ns 200045 B
10000 10 138,594.42 ns 2,527.979 ns 2,364.673 ns 200045 B
10000 100 NA NA NA NA
100000 10 NA NA NA NA
100000 100 NA NA NA NA
Log 10 scale

If we compare this method to the standard + operator we get:

  • 0.13 ms instead of 101.5 ms
  • 200KB instead of 976MB

While this method is the fastest among the methods presented in this article you need to be aware of the stack space limitation for larger strings.


Exploring Less Common Alternatives

In addition to the methods mentioned above, C# provides a few less common or specialized ways to concatenate strings. These may be useful in certain scenarios:

  • Using LINQ: You can use LINQ methods like Aggregate to concatenate strings in a sequence. While not the most efficient way, it's useful in specific situations.
  • Custom String Concatenation Methods: You can create your custom methods or extension methods to concatenate strings according to your specific requirements.

LINQ

Using the method Aggregate from LINQ we can concatenate string from a collection. While some like it for the use of lambda expression to add each individual string to the accumulation you must be aware that this method can cause more memory allocation because of the use of intermediary strings for each iteration.

public string LINQMethod()
{
    return strings.Aggregate(
        "", // start with empty string to handle empty list case.
        (current, next) => current + ", " + next);
}

public string LINQStringBuilderMethod()
{
    return strings.Aggregate(
        new StringBuilder(),
        (current, next) => current
            .Append(current.Length == 0 ? "" : ", ")
             .Append(next))
        .ToString();
}
# Strings String Length Mean Error StdDev Allocated
+
10 10 260.1 ns 5.22 ns 11.88 ns 1600 B
10 100 653.5 ns 13.08 ns 27.60 ns 11520 B
100 10 6,625.2 ns 129.96 ns 182.19 ns 123640 B
100 100 40,991.1 ns 797.98 ns 1,092.28 ns 1032840 B
1000 10 450,842.3 ns 12,048.05 ns 35,144.72 ns 12036040 B
1000 100 8,844,008.4 ns 175,218.23 ns 201,781.58 ns 102136852 B
10000 10 129,971,872.9 ns 2,551,239.05 ns 3,317,331.40 ns 1200469198 B
10000 100 2,821,343,535.7 ns 51,988,297.99 ns 46,086,272.03 ns 10201675080 B
100000 10 43,101,105,766.7 ns 681,921,394.90 ns 637,869,695.88 ns 120004956472 B
100000 100 223,158,413,106.7 ns 2,806,368,764.13 ns 2,625,079,083.16 ns 1020016585608 B
String Builder
10 10 226.4 ns 4.38 ns 4.69 ns 848 B
10 100 407.2 ns 6.80 ns 5.68 ns 5736 B
100 10 1,763.1 ns 33.02 ns 30.89 ns 7136 B
100 100 3,054.2 ns 44.83 ns 39.74 ns 46712 B
1000 10 14,779.6 ns 184.01 ns 172.12 ns 57240 B
1000 100 113,960.2 ns 1,217.77 ns 1,016.90 ns 423197 B
10000 10 262,276.2 ns 2,309.95 ns 2,160.73 ns 482200 B
10000 100 1,541,814.3 ns 29,650.71 ns 68,127.44 ns 4092021 B
100000 10 2,593,545.7 ns 55,972.05 ns 164,156.33 ns 4813275 B
100000 100 24,696,946.8 ns 489,509.89 ns 1,074,486.07 ns 40910248 B
Log 10 scale

We can see that the + operator is slow and has a higher memory allocation than StringBuilder approach.

All methods benchmarked - small strings

All the methods explained in this post but we are looking at a sample of 10, 100, 1000 strings of a length of 10, 100

Performance result 10, 100, 1000 with length of 10, 100
# Strings String Length Mean Error StdDev Allocated
Span
10 10 56.79 ns 1.154 ns 1.830 ns 224 B
10 100 181.87 ns 3.682 ns 9.505 ns 2024 B
100 10 501.36 ns 8.935 ns 10.289 ns 2024 B
100 100 1,948.42 ns 38.507 ns 64.337 ns 20024 B
1000 10 5,163.55 ns 47.698 ns 44.617 ns 20024 B
1000 100 94,934.28 ns 1,892.397 ns 2,252.764 ns 200045 B
String.Join
10 10 71.82 ns 1.362 ns 1.569 ns 224 B
10 100 138.47 ns 2.810 ns 5.548 ns 2024 B
100 10 712.11 ns 13.488 ns 15.533 ns 2024 B
100 100 1,327.22 ns 26.586 ns 66.699 ns 20024 B
1000 10 6,952.19 ns 132.831 ns 124.250 ns 20024 B
1000 100 82,692.78 ns 1,621.561 ns 1,516.809 ns 200045 B
String.Concat
10 10 115.58 ns 2.295 ns 2.357 ns 264 B
10 100 264.78 ns 5.297 ns 10.078 ns 2064 B
100 10 1,073.04 ns 21.370 ns 29.251 ns 2064 B
100 100 6,387.12 ns 113.094 ns 105.788 ns 20064 B
1000 10 14,019.86 ns 207.155 ns 193.773 ns 20064 B
1000 100 105,150.19 ns 1,676.485 ns 1,568.185 ns 200085 B
StringBuilder Join
10 10 222.6 ns 4.42 ns 4.14 ns 808 B
10 100 401.3 ns 7.89 ns 9.98 ns 5696 B
100 10 1,589.5 ns 27.32 ns 26.84 ns 4616 B
100 100 2,852.6 ns 49.98 ns 71.68 ns 46312 B
1000 10 14,358.2 ns 216.63 ns 192.04 ns 53240 B
1000 100 112,916.9 ns 2,153.84 ns 2,304.59 ns 403125 B
StringBuilder +
10 10 115.7 ns 1.91 ns 1.69 ns 768 B
10 100 311.8 ns 6.21 ns 9.30 ns 5656 B
100 10 702.3 ns 10.24 ns 7.99 ns 4576 B
100 100 2,279.4 ns 45.29 ns 74.42 ns 46272 B
1000 10 5,814.9 ns 95.14 ns 89.00 ns 53200 B
1000 100 105,793.9 ns 1,694.21 ns 1,501.87 ns 403085 B
+
10 10 121.9 ns 1.90 ns 1.69 ns 1280 B
10 100 526.9 ns 9.85 ns 20.35 ns 10760 B
100 10 4,877.5 ns 95.51 ns 164.75 ns 101130 B
100 100 39,929.5 ns 781.16 ns 1,347.46 ns 988450 B
1000 10 409,115.8 ns 8,177.66 ns 20,365.22 ns 9800730 B
1000 100 8,728,488.6 ns 142,505.51 ns 118,998.55 ns 97785500 B
LINQ
10 10 260.1 ns 5.22 ns 11.88 ns 1600 B
10 100 653.5 ns 13.08 ns 27.60 ns 11520 B
100 10 6,625.2 ns 129.96 ns 182.19 ns 123640 B
100 100 40,991.1 ns 797.98 ns 1,092.28 ns 1032840 B
1000 10 450,842.3 ns 12,048.05 ns 35,144.72 ns 12036040 B
1000 100 8,844,008.4 ns 175,218.23 ns 201,781.58 ns 102136852 B
LINQ StringBuilder
10 10 226.4 ns 4.38 ns 4.69 ns 848 B
10 100 407.2 ns 6.80 ns 5.68 ns 5736 B
100 10 1,763.1 ns 33.02 ns 30.89 ns 7136 B
100 100 3,054.2 ns 44.83 ns 39.74 ns 46712 B
1000 10 14,779.6 ns 184.01 ns 172.12 ns 57240 B
1000 100 113,960.2 ns 1,217.77 ns 1,016.90 ns 423197 B
Log 10 scale
Log 10 scale

As we can observe in the raw result data or the images above Span, String.Join and String.Concat are the best to use for small scale strings.

All methods benchmarked - big strings

Here we group all the methods but we look a the higher samples - 10,000, 100,000 elements

Performance result 10,000, 100,000 with length of 10, 100
# Strings String Length Mean Error StdDev Allocated
Span
10000 10 138,594.42 ns 2,527.979 ns 2,364.673 ns 200045 B
10000 100 NA NA NA NA
100000 10 NA NA NA NA
100000 100 NA NA NA NA
String.Join
10000 10 141,403.33 ns 1,871.830 ns 1,750.911 ns 200045 B
10000 100 449,650.15 ns 7,860.112 ns 6,967.785 ns 2000102 B
100000 10 913,902.50 ns 8,593.624 ns 7,618.024 ns 2000091 B
100000 100 6,929,308.96 ns 124,949.937 ns 116,878.249 ns 20000161 B
String.Concat
10000 10 183,683.38 ns 2,569.525 ns 2,403.535 ns 200085 B
10000 100 625,732.25 ns 5,523.159 ns 5,166.366 ns 2001236 B
100000 10 1,192,823.37 ns 14,475.475 ns 13,540.368 ns 2001047 B
100000 100 9,781,831.75 ns 195,450.444 ns 456,859.112 ns 20001636 B
StringBuilder Join
10000 10 228,084.0 ns 1,721.81 ns 1,610.59 ns 410053 B
10000 100 1,023,579.2 ns 19,992.55 ns 37,550.85 ns 4019695 B
100000 10 1,876,677.7 ns 37,503.34 ns 60,560.91 ns 4011143 B
100000 100 27,236,426.6 ns 544,572.27 ns 847,833.42 ns 40108448 B
StringBuilder +
10000 10 151,350.0 ns 1,431.72 ns 1,339.23 ns 410013 B
10000 100 1,203,541.6 ns 24,496.60 ns 72,228.77 ns 4019591 B
100000 10 1,907,386.3 ns 36,858.46 ns 39,438.14 ns 4011056 B
100000 100 27,886,235.4 ns 546,256.10 ns 765,774.76 ns 40108408 B
+
10000 10 101,500,933.6 ns 1,982,547.69 ns 2,434,746.60 ns 976997650 B
10000 100 2,913,516,093.8 ns 55,183,820.69 ns 54,197,890.65 ns 9767232680 B
100000 10 34,432,751,564.3 ns 221,287,906.99 ns 196,165,965.67 ns 97660877270 B
100000 100 221,595,051,628.6 ns 1,408,263,963.50 ns 1,248,389,322.65 ns 976578167180 B
LINQ
10000 10 129,971,872.9 ns 2,551,239.05 ns 3,317,331.40 ns 1200469198 B
10000 100 2,821,343,535.7 ns 51,988,297.99 ns 46,086,272.03 ns 10201675080 B
100000 10 43,101,105,766.7 ns 681,921,394.90 ns 637,869,695.88 ns 120004956472 B
100000 100 223,158,413,106.7 ns 2,806,368,764.13 ns 2,625,079,083.16 ns 1020016585608 B
LINQ StringBuilder
10000 10 262,276.2 ns 2,309.95 ns 2,160.73 ns 482200 B
10000 100 1,541,814.3 ns 29,650.71 ns 68,127.44 ns 4092021 B
100000 10 2,593,545.7 ns 55,972.05 ns 164,156.33 ns 4813275 B
100000 100 24,696,946.8 ns 489,509.89 ns 1,074,486.07 ns 40910248 B

Conclusion

As seen there are several methods one can use to concatenate a number of strings. Choosing the one to use is a decision that needs to take into account several factors besides the memory efficiency and performance. Other factors include (but not limited):

  • code readability
  • ease of maintenance

I hope that the methods and benchmarks provided will help your decision making for your next project.

Benchmark system

All the benchmarks were run on the following configuration:

  • BenchmarkDotNet v0.13.9
  • Windows 11
  • CPU: AMD Ryzen 7 5800X
  • RAM: 32GB
  • .NET: 7.0.402