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:
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:
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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