345

What's the most efficient way to concatenate strings?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
jimmij
  • 3,501
  • 2
  • 19
  • 6
  • 11
    I want to place a prominent warning here that the accepted answer is significantly incomplete because it does not discuss all relevant cases. – usr Jun 23 '16 at 13:57
  • 1
    @usr Indeed... more detailed information about `StringBuilder` usage cases can be found [here](http://stackoverflow.com/a/21644889/3256506). – Tamir Vered Jan 26 '17 at 13:00
  • 1
    My new favorite as of C# 6 is $"Constant text here {foo} and {bar}"... it's like `String.Format` on steroids. Which, performance wise, is a tiny bit slower at one liners than `+` and `String.Concat`, but much better than those, albeit slower than `StringBuilder`, at multiple calls. Practically speaking, the performance differences are such that, if I had to choose only one way to concatenate, I would choose string interpolations using `$`... If two ways, then add `StringBuilder` to my toolbox. With those two ways you're set. – u8it Sep 22 '17 at 20:54
  • The `String.Join` answer below doesn't do `+` justice and is, practically speaking, a bad way to concatenate strings, but it is surprisingly fast performance wise. The answer why is interesting. `String.Concat` and `String.Join` can both act on arrays, but `String.Join` is actually faster. Apparently, `String.Join` is pretty sophisticated and more optimized than `String.Concat`, partly because it operates similarly to `StringBuilder` in that it calculates string length first and then constructs the string benefiting from this knowledge using UnSafeCharBuffer. – u8it Sep 22 '17 at 21:22
  • Ok, so it's fast, but `String.Join` also requires constructing an array which seems resource inefficient right?... Turns out that `+` and `String.Concat` construct arrays for their constituents anyway. Consequently, manually creating an array and feeding it to `String.Join` is comparatively faster... however, `StringBuilder` still outperforms `String.Join` in about every practical way while `$` is only slightly slower and much faster at long strings… not to mention that it’s awkward and ugly to use `String.Join` if you have to create an array for it on the spot. – u8it Sep 22 '17 at 21:22
  • More info about [`String.Join` vs `StringBuilder`](https://stackoverflow.com/q/585860/3546415) – u8it Sep 22 '17 at 21:26

18 Answers18

291

Rico Mariani, the .NET Performance guru, had an article on this very subject. It's not as simple as one might suspect. The basic advice is this:

If your pattern looks like:

x = f1(...) + f2(...) + f3(...) + f4(...)

that's one concat and it's zippy, StringBuilder probably won't help.

If your pattern looks like:

if (...) x += f1(...)
if (...) x += f2(...)
if (...) x += f3(...)
if (...) x += f4(...)

then you probably want StringBuilder.

Yet another article to support this claim comes from Eric Lippert where he describes the optimizations performed on one line + concatenations in a detailed manner.

Todd Ropog
  • 1,133
  • 1
  • 14
  • 23
Lee
  • 18,529
  • 6
  • 58
  • 60
170

The StringBuilder.Append() method is much better than using the + operator. But I've found that, when executing 1000 concatenations or less, String.Join() is even more efficient than StringBuilder.

StringBuilder sb = new StringBuilder();
sb.Append(someString);

The only problem with String.Join is that you have to concatenate the strings with a common delimiter.

Edit: as @ryanversaw pointed out, you can make the delimiter string.Empty.

string key = String.Join("_", new String[] 
{ "Customers_Contacts", customerID, database, SessionID });
Yousha Aleayoub
  • 4,532
  • 4
  • 53
  • 64
TheEmirOfGroofunkistan
  • 5,476
  • 8
  • 37
  • 53
  • 16
    `StringBuilder` has a huge comparable start-up cost, it's only efficient when used with very large strings, or very many concatenations. It isn't trivial to find out for any given situation. If performance is of issue, profiling is your friend (check ANTS). – Abel Nov 04 '09 at 13:22
  • 37
    This is not true for single line concatenation. Say you do myString = "foo" + var1 + "bar" + var2 + "hello" + var3 + "world", the compiler automatically turns that into a string.concat call, which is as efficient as it gets. This answer is incorrect, there are plenty of better answers to choose from – csauve Jan 12 '11 at 17:56
  • 2
    For trivial string concatentation use what ever is most readable. string a = b + c + d; will almost always be faster than doing it with StringBuilder, but the difference is typically irrelevant. Use StringBuilder (or other option of your choice) when repeatedly adding to the same string (eg. building up a report) or when dealing with large strings. – Swanny Jun 26 '11 at 10:28
  • 6
    Why haven't you mentioned `string.Concat`? – Venemo Sep 27 '13 at 12:56
  • 3
    this helped in the year 2021 as well. :) – vip007 May 14 '21 at 15:18
  • 1
    I have an old laptop (Dell N5050 Laptop). I was shocked, after I replace all string concatenations with the StringBuilder it was processed in 7 seconds. In normal concatenation (i.e using +) was 1 hour long and still was running. Increible how unoptimized is doing concatenation with '+' operator. Increible! – Jordi Espada Aug 12 '21 at 14:16
100

There are 6 types of string concatenations:

  1. Using the plus (+) symbol.
  2. Using string.Concat().
  3. Using string.Join().
  4. Using string.Format().
  5. Using string.Append().
  6. Using StringBuilder.

In an experiment, it has been proved that string.Concat() is the best way to approach if the words are less than 1000(approximately) and if the words are more than 1000 then StringBuilder should be used.

For more information, check this site.

string.Join() vs string.Concat()

The string.Concat method here is equivalent to the string.Join method invocation with an empty separator. Appending an empty string is fast, but not doing so is even faster, so the string.Concat method would be superior here.

SiHa
  • 7,830
  • 13
  • 34
  • 43
Mr_Green
  • 40,727
  • 45
  • 159
  • 271
  • 10
    Should read it has been proved string.Concat() or + is the best way. Yes I can get this from the article but it saves me one click. So, + and concat compile into the same code. – brumScouse Aug 07 '15 at 13:51
  • I used this basis to try and make a method of mine more efficient, where I was only needing to concatenate exactly 3 strings. I found that `+` was actually 3 milliseconds faster than `string.Concat()`, though I have not looked into the amount of strings required before `string.Concat()` outraces `+`. – Gnemlock Nov 09 '18 at 06:13
  • 1
    7. String Interpolation. – John Stock Nov 25 '20 at 21:48
  • 1
    5. There is no `string.Append()`. – Lauren Rutledge Jul 29 '22 at 18:30
68

From Chinh Do - StringBuilder is not always faster:

Rules of Thumb

  • When concatenating three dynamic string values or less, use traditional string concatenation.

  • When concatenating more than three dynamic string values, use StringBuilder.

  • When building a big string from several string literals, use either the @ string literal or the inline + operator.

Most of the time StringBuilder is your best bet, but there are cases as shown in that post that you should at least think about each situation.

Yousha Aleayoub
  • 4,532
  • 4
  • 53
  • 64
palehorse
  • 26,407
  • 4
  • 40
  • 48
  • 9
    afaik @ only turns off escape sequences processing. http://msdn.microsoft.com/en-us/library/362314fe.aspx agree – abatishchev Jul 18 '10 at 20:11
12

If you're operating in a loop, StringBuilder is probably the way to go; it saves you the overhead of creating new strings regularly. In code that'll only run once, though, String.Concat is probably fine.

However, Rico Mariani (.NET optimization guru) made up a quiz in which he stated at the end that, in most cases, he recommends String.Format.

Yousha Aleayoub
  • 4,532
  • 4
  • 53
  • 64
Adam V
  • 6,256
  • 3
  • 40
  • 52
  • I've been recommending the use of string.format over string + string for years to people I've worked with. I think the readability advantages are an additional advantage beyond the performance benefit. – Scott Lawrence Nov 14 '08 at 18:38
  • 1
    This is the actual correct answer. The currently accepted answer for StringBuilder is incorrect, as it does not mention single line appends for which string.concat or + is faster. Little known fact is that the compiler actually translates +'s into string.concat's. Also, for loops or for multiple line concats I use a custom built string builder that only appends when .ToString is called - overcoming the indeterminate buffer problem that StringBuilder has – csauve Jan 12 '11 at 17:53
  • 2
    string.Format is not the fastest way under any circumstances. I don't know how to contrive a case where it comes ahead. – usr Jun 23 '16 at 13:55
  • @usr - note that Rico is explicitly not saying it's the *fastest*, just that it's his recommendation: "Even though it's the worst performing, and we knew that much in advance, both of your CLR Performance Architects concur that [string.Format] should be the default choice. In the highly unlikely event that it becomes a perf problem the issue is readily addressable with only modest local changes. Normally you're just cashing-in on some nice maintainability." – Adam V Apr 30 '18 at 14:00
  • @AdamV the question is about the fastest way. I disgree about it being the default choice though not for perf reasons. It can be clumsy syntax. Resharper can convert back and forth at will. – usr May 08 '18 at 16:13
12

Here is the fastest method I've evolved over a decade for my large-scale NLP app. I have variations for IEnumerable<T> and other input types, with and without separators of different types (Char, String), but here I show the simple case of concatenating all strings in an array into a single string, with no separator. Latest version here is developed and unit-tested on C# 7 and .NET 4.7.

There are two keys to higher performance; the first is to pre-compute the exact total size required. This step is trivial when the input is an array as shown here. For handling IEnumerable<T> instead, it is worth first gathering the strings into a temporary array for computing that total (The array is required to avoid calling ToString() more than once per element since technically, given the possibility of side-effects, doing so could change the expected semantics of a 'string join' operation).

Next, given the total allocation size of the final string, the biggest boost in performance is gained by building the result string in-place. Doing this requires the (perhaps controversial) technique of temporarily suspending the immutability of a new String which is initially allocated full of zeros. Any such controversy aside, however...

...note that this is the only bulk-concatenation solution on this page which entirely avoids an extra round of allocation and copying by the String constructor.

Complete code:

/// <summary>
/// Concatenate the strings in 'rg', none of which may be null, into a single String.
/// </summary>
public static unsafe String StringJoin(this String[] rg)
{
    int i;
    if (rg == null || (i = rg.Length) == 0)
        return String.Empty;

    if (i == 1)
        return rg[0];

    String s, t;
    int cch = 0;
    do
        cch += rg[--i].Length;
    while (i > 0);
    if (cch == 0)
        return String.Empty;

    i = rg.Length;
    fixed (Char* _p = (s = new String(default(Char), cch)))
    {
        Char* pDst = _p + cch;
        do
            if ((t = rg[--i]).Length > 0)
                fixed (Char* pSrc = t)
                    memcpy(pDst -= t.Length, pSrc, (UIntPtr)(t.Length << 1));
        while (pDst > _p);
    }
    return s;
}

[DllImport("MSVCR120_CLR0400", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe void* memcpy(void* dest, void* src, UIntPtr cb);

I should mention that this code has a slight modification from what I use myself. In the original, I call the cpblk IL instruction from C# to do the actual copying. For simplicity and portability in the code here, I replaced that with P/Invoke memcpy instead, as you can see. For highest performance on x64 (but maybe not x86) you may want to use the cpblk method instead.

Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
  • `string.Join` does all of these things already for you. There's no need to write it yourself. It computes the size of the final string, constructs a string of that size, and then writes out to the underlying character array. It even has the bonus of using readable variable names in the process. – Servy Nov 10 '17 at 22:09
  • 1
    @Servy Thanks for the comment; indeed `String.Join` can be efficient. As I hinted in the intro, the code here is just the simplest illustration of a family of functions I use for scenarios that `String.Join` either does not handle (such as optimizing for `Char` separator) or did not handle in previous versions of .NET. I suppose I shouldn't have picked this for the simplest example, since it is a case that `String.Join` already handles well, albeit with the "inefficiency," likely unmeasurable, of processing a vacuous separator, viz. `String.Empty`. – Glenn Slayden Nov 10 '17 at 22:22
  • Sure, in the case that you don't have a separator, then you should call `Concat`, which *also* does this correctly. Either way you don't need to write the code yourself. – Servy Nov 10 '17 at 22:25
  • 8
    @Servy I've compared the performance of `String.Join` versus my code using [this test harness](https://pastebin.com/28x747Hw). For 10 million random concatenation operations of up to 100 word-sized strings each, the code shown above is consistently 34% faster than `String.Join` on **x64** release build with **.NET 4.7**. Since the OP explicitly requests the "most efficient" method, the result suggests that my answer applies. If this addresses your concerns, I invite you to reconsider your downvote. – Glenn Slayden Nov 12 '17 at 03:39
  • This looks pretty neat! – Zimano Dec 14 '17 at 20:22
  • 2
    I recently benchmarked this on x64 full CLR 4.7.1 and find it to be about twice as fast as string.Join with about 25% less memory allocated (https://i.imgur.com/SxIpEmL.png) when using cpblk or https://github.com/JonHanna/Mnemosyne – quentin-starin Apr 22 '18 at 21:18
  • Any reason MS wouldn't incorporate this code into String.Join if it really is faster? Are there some drawbacks that stop them doing so? – rollsch Jan 30 '20 at 06:41
  • @rolls I suspect in this case there's not nearly enough reason to change such a central locus of **.NET** functionality. For example, even a simple change that only increases performance could be a breaking change for (admittedly and certainly flawed) bad apps that depend on existing--and very long-standing--performance characteristics to avoid races or other correctness conditions. – Glenn Slayden Feb 03 '20 at 20:28
8

From this MSDN article:

There is some overhead associated with creating a StringBuilder object, both in time and memory. On a machine with fast memory, a StringBuilder becomes worthwhile if you're doing about five operations. As a rule of thumb, I would say 10 or more string operations is a justification for the overhead on any machine, even a slower one.

So if you trust MSDN go with StringBuilder if you have to do more than 10 strings operations/concatenations - otherwise simple string concat with '+' is fine.

JohnIdol
  • 48,899
  • 61
  • 158
  • 242
6

Try this 2 pieces of code and you will find the solution.

 static void Main(string[] args)
    {
        StringBuilder s = new StringBuilder();
        for (int i = 0; i < 10000000; i++)
        {
            s.Append( i.ToString());
        }
        Console.Write("End");
        Console.Read();
    }

Vs

static void Main(string[] args)
    {
        string s = "";
        for (int i = 0; i < 10000000; i++)
        {
            s += i.ToString();
        }
        Console.Write("End");
        Console.Read();
    }

You will find that 1st code will end really quick and the memory will be in a good amount.

The second code maybe the memory will be ok, but it will take longer... much longer. So if you have an application for a lot of users and you need speed, use the 1st. If you have an app for a short term one user app, maybe you can use both or the 2nd will be more "natural" for developers.

Cheers.

Eduardo Mass
  • 79
  • 1
  • 2
5

It's also important to point it out that you should use the + operator if you are concatenating string literals.

When you concatenate string literals or string constants by using the + operator, the compiler creates a single string. No run time concatenation occurs.

How to: Concatenate Multiple Strings (C# Programming Guide)

talles
  • 14,356
  • 8
  • 45
  • 58
5

Adding to the other answers, please keep in mind that StringBuilder can be told an initial amount of memory to allocate.

The capacity parameter defines the maximum number of characters that can be stored in the memory allocated by the current instance. Its value is assigned to the Capacity property. If the number of characters to be stored in the current instance exceeds this capacity value, the StringBuilder object allocates additional memory to store them.

If capacity is zero, the implementation-specific default capacity is used.

Repeatedly appending to a StringBuilder that hasn't been pre-allocated can result in a lot of unnecessary allocations just like repeatedly concatenating regular strings.

If you know how long the final string will be, can trivially calculate it, or can make an educated guess about the common case (allocating too much isn't necessarily a bad thing), you should be providing this information to the constructor or the Capacity property. Especially when running performance tests to compare StringBuilder with other methods like String.Concat, which do the same thing internally. Any test you see online which doesn't include StringBuilder pre-allocation in its comparisons is wrong.

If you can't make any kind of guess about the size, you're probably writing a utility function which should have its own optional argument for controlling pre-allocation.

Community
  • 1
  • 1
DBN
  • 374
  • 4
  • 10
5

Following may be one more alternate solution to concatenate multiple strings.

String str1 = "sometext";
string str2 = "some other text";

string afterConcate = $"{str1}{str2}";

string interpolation

u8it
  • 3,956
  • 1
  • 20
  • 33
RP Nainwal
  • 305
  • 5
  • 8
  • 3
    This is actually surprising nice as a general method of concatenation. It's basically `String.Format` but more readable and easier to work with. Bench-marking it, it's slightly slower than `+` and `String.Concat` at one line concatenations but much better than both of those at repetitive calls making `StringBuilder` less necessary. – u8it Sep 22 '17 at 20:24
  • 2
    Under the hood, string interpolation uses String.Format, and under the hood String.Format uses a cached StringBuilder. String interpolation is a great addition to the C#6 language. – Matt Parkins Jul 13 '20 at 13:12
3

Another solution:

inside the loop, use List instead of string.

List<string> lst= new List<string>();

for(int i=0; i<100000; i++){
    ...........
    lst.Add(...);
}
return String.Join("", lst.ToArray());;

it is very very fast.

asady
  • 57
  • 4
3

I've tested all the methods in this page and at the end I've developed my solution that is the fastest and less memory expensive.

Note: tested in Framework 4.8

enter image description here

 [MemoryDiagnoser]
public class StringConcatSimple
{
    private string
        title = "Mr.", firstName = "David", middleName = "Patrick", lastName = "Callan";

    [Benchmark]
    public string FastConcat()
    {
        return FastConcat(
            title, " ", 
            firstName, " ",
            middleName, " ", 
            lastName);
    }

    [Benchmark]
    public string StringBuilder()
    {
        var stringBuilder =
            new StringBuilder();

        return stringBuilder
            .Append(title).Append(' ')
            .Append(firstName).Append(' ')
            .Append(middleName).Append(' ')
            .Append(lastName).ToString();
    }

    [Benchmark]
    public string StringBuilderExact24()
    {
        var stringBuilder =
            new StringBuilder(24);

        return stringBuilder
            .Append(title).Append(' ')
            .Append(firstName).Append(' ')
            .Append(middleName).Append(' ')
            .Append(lastName).ToString();
    }

    [Benchmark]
    public string StringBuilderEstimate100()
    {
        var stringBuilder =
            new StringBuilder(100);

        return stringBuilder
            .Append(title).Append(' ')
            .Append(firstName).Append(' ')
            .Append(middleName).Append(' ')
            .Append(lastName).ToString();
    }

    [Benchmark]
    public string StringPlus()
    {
        return title + ' ' + firstName + ' ' +
            middleName + ' ' + lastName;
    }

    [Benchmark]
    public string StringFormat()
    {
        return string.Format("{0} {1} {2} {3}",
            title, firstName, middleName, lastName);
    }

    [Benchmark]
    public string StringInterpolation()
    {
        return
        $"{title} {firstName} {middleName} {lastName}";
    }

    [Benchmark]
    public string StringJoin()
    {
        return string.Join(" ", title, firstName,
            middleName, lastName);
    }

    [Benchmark]
    public string StringConcat()
    {
        return string.
            Concat(new String[]
            { title, " ", firstName, " ",
                middleName, " ", lastName });
    }
}

Yes, it use unsafe

public static unsafe string FastConcat(string str1, string str2, string str3, string str4, string str5, string str6, string str7)
    {
        var capacity = 0;

        var str1Length = 0;
        var str2Length = 0;
        var str3Length = 0;
        var str4Length = 0;
        var str5Length = 0;
        var str6Length = 0;
        var str7Length = 0;

        if (str1 != null)
        {
            str1Length = str1.Length;
            capacity = str1Length;
        }

        if (str2 != null)
        {
            str2Length = str2.Length;
            capacity += str2Length;
        }

        if (str3 != null)
        {
            str3Length = str3.Length;
            capacity += str3Length;
        }

        if (str4 != null)
        {
            str4Length = str4.Length;
            capacity += str4Length;
        }

        if (str5 != null)
        {
            str5Length = str5.Length;
            capacity += str5Length;
        }

        if (str6 != null)
        {
            str6Length = str6.Length;
            capacity += str6Length;
        }

        if (str7 != null)
        {
            str7Length = str7.Length;
            capacity += str7Length;
        }


        string result = new string(' ', capacity);

        fixed (char* dest = result)
        {
            var x = dest;

            if (str1Length > 0)
            {
                fixed (char* src = str1)
                {
                    Unsafe.CopyBlock(x, src, (uint)str1Length * 2); 
                    x += str1Length;
                }
            }

            if (str2Length > 0)
            {
                fixed (char* src = str2)
                {
                    Unsafe.CopyBlock(x, src, (uint)str2Length * 2);
                    x += str2Length;
                }
            }

            if (str3Length > 0)
            {
                fixed (char* src = str3)
                {
                    Unsafe.CopyBlock(x, src, (uint)str3Length * 2);
                    x += str3Length;
                }
            }

            if (str4Length > 0)
            {
                fixed (char* src = str4)
                {
                    Unsafe.CopyBlock(x, src, (uint)str4Length * 2);
                    x += str4Length;
                }
            }

            if (str5Length > 0)
            {
                fixed (char* src = str5)
                {
                    Unsafe.CopyBlock(x, src, (uint)str5Length * 2);
                    x += str5Length;
                }
            }

            if (str6Length > 0)
            {
                fixed (char* src = str6)
                {
                    Unsafe.CopyBlock(x, src, (uint)str6Length * 2);
                    x += str6Length;
                }
            }

            if (str7Length > 0)
            {
                fixed (char* src = str7)
                {
                    Unsafe.CopyBlock(x, src, (uint)str7Length * 2);
                }
            }
        }

        return result;
    }

You can edit the method and adapt it to your case. For example you can make it something like

public static unsafe string FastConcat(string str1, string str2, string str3 = null, string str4 = null, string str5 = null, string str6 = null, string str7 = null)

bertasoft
  • 457
  • 6
  • 12
  • 1
    I also want to add for anyone reading this later that I tested it in .Net 6 and .Net 7, and for both, the unsafe solution is still the fastest, with String.Join being in 2nd place for speed. But the memory allocation for String Interpolation is the same as unsafe (72 B). – steven01804 Dec 08 '22 at 09:31
  • The question didn't specify the .Net framework version. So I was thinking... wouldn't using String.Create(), that leverages the span and spanaction, create a safe version of the FastConcat with the same characteristic as the unsafe version? – Erik de Roos May 25 '23 at 10:12
  • @ErikdeRoos did you try that idea out and benchmark it? – Paul Carlton Jun 17 '23 at 01:53
2

The most efficient is to use StringBuilder, like so:

StringBuilder sb = new StringBuilder();
sb.Append("string1");
sb.Append("string2");
...etc...
String strResult = sb.ToString();

@jonezy: String.Concat is fine if you have a couple of small things. But if you're concatenating megabytes of data, your program will likely tank.

TheSmurf
  • 15,337
  • 3
  • 40
  • 48
2

System.String is immutable. When we modify the value of a string variable then a new memory is allocated to the new value and the previous memory allocation released. System.StringBuilder was designed to have concept of a mutable string where a variety of operations can be performed without allocation separate memory location for the modified string.

Dhibi_Mohanned
  • 283
  • 1
  • 7
  • 15
  • this is not completely true. Separate memory location would be allocated once the string size exceeds the default capacity of the StringBuilder class. Given you can predict the max size of the string upfront, you can avoid memory allocations by setting an appropriate capacity upfront. https://learn.microsoft.com/en-us/dotnet/api/system.text.stringbuilder?view=netcore-3.1 – supi Aug 30 '20 at 05:08
1

For just two strings, you definitely do not want to use StringBuilder. There is some threshold above which the StringBuilder overhead is less than the overhead of allocating multiple strings.

So, for more that 2-3 strings, use DannySmurf's code. Otherwise, just use the + operator.

Community
  • 1
  • 1
Nick
  • 13,238
  • 17
  • 64
  • 100
1

It really depends on your usage pattern. A detailed benchmark between string.Join, string,Concat and string.Format can be found here: String.Format Isn't Suitable for Intensive Logging

(This is actually the same answer I gave to this question)

Liran
  • 1,596
  • 1
  • 13
  • 11
0

It would depend on the code. StringBuilder is more efficient generally, but if you're only concatenating a few strings and doing it all in one line, code optimizations will likely take care of it for you. It's important to think about how the code looks too: for larger sets StringBuilder will make it easier to read, for small ones StringBuilder will just add needless clutter.

Jon Dewees
  • 2,957
  • 6
  • 31
  • 38