403

What is the most efficient way to write the old-school:

StringBuilder sb = new StringBuilder();
if (strings.Count > 0)
{
    foreach (string s in strings)
    {
        sb.Append(s + ", ");
    }
    sb.Remove(sb.Length - 2, 2);
}
return sb.ToString();

...in LINQ?

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
tags2k
  • 82,117
  • 31
  • 79
  • 106
  • 1
    Did you discover any other super cool LINQ ways of doing things? – Robert S. Jan 23 '09 at 15:44
  • 4
    Well the selected answer and all the other options don't work in Linq to Entities. – Binoj Antony Jul 30 '09 at 12:40
  • 3
    @Binoj Antony, don't make your database do string concatenation. – Amy B Jun 08 '10 at 15:18
  • 6
    @Pr0fess0rX: Because it can't and because it shouldn't. I don't know about other databases but in SQL Server you can only concat (n)varcahr which limits you to (n)varchar(max). It shouldn't because business logic shouldn't be implemented in the data layer. – the_drow Apr 27 '11 at 10:36
  • None of the answers work with the EntityFramework - see the comment I placed below the marked answer. Does anyone know a solution? – Matt Feb 10 '14 at 11:51
  • If anyone does do this old school way, you could replace sb.Remove(sb.Length - 2, 2); with sb.Length -= 2; – PRMan May 21 '16 at 16:49
  • I was using: StringBuilder sb = new StringBuilder(); AttachmentsStringList.Select(a => sb.Append(a + ",")); return sb.ToString(); I search for this to see if there was a better way. I haven't tested that yet. – John Foll Mar 23 '23 at 17:01

17 Answers17

577

This answer shows usage of LINQ (Aggregate) as requested in the question and is not intended for everyday use. Because this does not use a StringBuilder it will have horrible performance for very long sequences. For regular code use String.Join as shown in the other answer

Use aggregate queries like this:

string[] words = { "one", "two", "three" };
var res = words.Aggregate(
   "", // start with empty string to handle empty list case.
   (current, next) => current + ", " + next);
Console.WriteLine(res);

This outputs:

, one, two, three

An aggregate is a function that takes a collection of values and returns a scalar value. Examples from T-SQL include min, max, and sum. Both VB and C# have support for aggregates. Both VB and C# support aggregates as extension methods. Using the dot-notation, one simply calls a method on an IEnumerable object.

Remember that aggregate queries are executed immediately.

More information - MSDN: Aggregate Queries


If you really want to use Aggregate use variant using StringBuilder proposed in comment by CodeMonkeyKing which would be about the same code as regular String.Join including good performance for large number of objects:

 var res = words.Aggregate(
     new StringBuilder(), 
     (current, next) => current.Append(current.Length == 0? "" : ", ").Append(next))
     .ToString();
sergtk
  • 10,714
  • 15
  • 75
  • 130
Jorge Ferreira
  • 96,051
  • 25
  • 122
  • 132
  • 4
    The first example doesn't output "one, two, three", it outputs ", one, two, three" (Notice the leading comma). – Mort Feb 06 '19 at 07:51
  • In your first example, since you seed with `""`, the first value used in `current` is an empty string. So, for 1 or more element, you'll always get `, ` at the start of the string. – Michael Yanni Mar 14 '19 at 01:08
  • @Mort I have fixed this – sergtk Aug 20 '19 at 15:53
436
return string.Join(", ", strings.ToArray());

In .Net 4, there's a new overload for string.Join that accepts IEnumerable<string>. The code would then look like:

return string.Join(", ", strings);
Amy B
  • 108,202
  • 21
  • 135
  • 185
  • 3
    OK, so the solution doesn't use Linq, but it seems to work pretty well to me – Mat Roberts Dec 22 '08 at 10:59
  • 27
    This is the most correct answer. It is faster than both the question and the accepted answer and is much clearer than Aggregate, which requires a paragraph-long explanation every time it is used. – PRMan May 21 '16 at 16:51
137

Why use Linq?

string[] s = {"foo", "bar", "baz"};
Console.WriteLine(String.Join(", ", s));

That works perfectly and accepts any IEnumerable<string> as far as I remember. No need Aggregate anything here which is a lot slower.

Armin Ronacher
  • 31,998
  • 13
  • 65
  • 69
  • 10
    .NET 4.0 has an IEnumerable and IEnumrable overload, which will make it much easier to use – Cine Jun 02 '10 at 06:30
  • 4
    As Cine points out, .NET 4.0 has the overload. Previous versions don't. You can still `String.Join(",", s.ToArray())` in the older versions though. – Martijn Jan 24 '11 at 14:30
  • 1
    FYI: merged from http://stackoverflow.com/questions/122670/what-is-the-linq-way-to-implode-join-a-string-array – Shog9 Dec 31 '13 at 16:36
  • @Shog9 Merging makes answers here look like duplicated efforts, and the timestamps dont help at all.. Still the way to go. – nawfal Jan 04 '14 at 12:06
  • @Armin: It could be useful if your source is streaming data instead of being a finite known size collection. Also, the streaming could be because the data comes in progressively. Then the LINQ solution could process data as it arrives instead of having to wait for the whole collection to be received. This way, if for instance only one value of a processed object is required, it is concatenated, the complex object is dropped and can then be recycled. – bkqc Aug 31 '16 at 17:47
81

Have you looked at the Aggregate extension method?

var sa = (new[] { "yabba", "dabba", "doo" }).Aggregate((a,b) => a + "," + b);
Robert S.
  • 25,266
  • 14
  • 84
  • 116
  • 25
    That's probably slower than String.Join(), and harder to read in code. Does answer the question for a "LINQ way", though :-) – Chris Wenham Sep 23 '08 at 18:12
  • 7
    Yeah, I didn't want to taint the answer with my opinions. :P – Robert S. Sep 23 '08 at 18:18
  • 2
    It's unquestionably quite a bit slower, actually. Even using Aggregate with a StringBuilder instead of concatenation is slower than String.Join. – Joel Mueller May 07 '09 at 20:33
  • This was the answer I was looking for - helped me understand Aggregate and the sort of things it's used for. – George Mauer Dec 01 '10 at 07:40
  • 4
    Made a test with 10.000.000 iterations, aggregate took 4.3 secs and string.join took 2.3 secs. So I would say the perf diff is unimportant for 99% of common use cases. So if you're already doing a lot of linq to process your data, there's usually no need to break that nice syntax and use string.join imo. https://gist.github.com/joeriks/5791981 – joeriks Jun 16 '13 at 13:05
  • @joeriks Did you do the tests with this crude implementation, or with a StringBuilder seed one? – julealgon Oct 02 '13 at 15:44
  • Re : Performance of the `String.Join` vs folding with `Aggregate` : http://devlifeasis.blogspot.com/2012/10/stringjoin-vs-linqaggregate.html – StuartLC Oct 28 '13 at 12:23
  • 1
    FYI: merged from http://stackoverflow.com/questions/122670/what-is-the-linq-way-to-implode-join-a-string-array – Shog9 Dec 31 '13 at 16:36
59

Real example from my code:

return selected.Select(query => query.Name).Aggregate((a, b) => a + ", " + b);

A query is an object that has a Name property which is a string, and I want the names of all the queries on the selected list, separated by commas.

Daniel Earwicker
  • 114,894
  • 38
  • 205
  • 284
34

Here is the combined Join/Linq approach I settled on after looking at the other answers and the issues addressed in a similar question (namely that Aggregate and Concatenate fail with 0 elements).

string Result = String.Join(",", split.Select(s => s.Name));

or (if s is not a string)

string Result = String.Join(",", split.Select(s => s.ToString()));

  • Simple
  • easy to read and understand
  • works for generic elements
  • allows using objects or object properties
  • handles the case of 0-length elements
  • could be used with additional Linq filtering
  • performs well (at least in my experience)
  • doesn't require (manual) creation of an additional object (e.g. StringBuilder) to implement

And of course Join takes care of the pesky final comma that sometimes sneaks into other approaches (for, foreach), which is why I was looking for a Linq solution in the first place.

brichins
  • 3,825
  • 2
  • 39
  • 60
  • 1
    miss-matched parenthesis. – ctrl-alt-delor Dec 18 '13 at 16:48
  • 1
    FYI: merged from http://stackoverflow.com/questions/122670/what-is-the-linq-way-to-implode-join-a-string-array – Shog9 Dec 31 '13 at 16:37
  • 4
    I like this answer because using `.Select()` like this provides an easy place to modify each element during this operation. For example, wrapping each item in some character like so `string Result = String.Join(",", split.Select(s => "'" + s + "'"));` – Sam Storie Dec 30 '14 at 20:11
29

You can use StringBuilder in Aggregate:

  List<string> strings = new List<string>() { "one", "two", "three" };

  StringBuilder sb = strings
    .Select(s => s)
    .Aggregate(new StringBuilder(), (ag, n) => ag.Append(n).Append(", "));

  if (sb.Length > 0) { sb.Remove(sb.Length - 2, 2); }

  Console.WriteLine(sb.ToString());

(The Select is in there just to show you can do more LINQ stuff.)

jonathan.s
  • 349
  • 3
  • 5
  • 2
    +1 nice. However, IMO it's better to avoid adding the extra "," than to erase it afterward. Something like `new[] {"one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) =>{if (sb.Length > 0) sb.Append(", ");sb.Append(s);return sb;}).ToString();` – dss539 May 19 '10 at 20:54
  • 5
    You would save precious clock cycles by not checking the `if (length > 0)` in the linq and by taking it out. – Binoj Antony Jun 09 '10 at 05:00
  • 1
    I agree with dss539. My version is along the lines of `new[] {"", "one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) => (String.IsNullOrEmpty(sb.ToString())) ? sb.Append(s) : sb.Append(", ").Append(s)).ToString();` – ProfNimrod Jan 14 '14 at 22:08
  • 1
    @ProfNimrod, Your code turns the StringBuffer into a string on every iteration (`sb.ToString()`). (It also checks for null something that could never be null.) You completely lose the advantage of the StringBuffer, and it’s as bad as just concatenating strings. – andrewf Nov 24 '20 at 14:29
22

quick performance data for the StringBuilder vs Select & Aggregate case over 3000 elements:

Unit test - Duration (seconds)
LINQ_StringBuilder - 0.0036644
LINQ_Select.Aggregate - 1.8012535

    [TestMethod()]
    public void LINQ_StringBuilder()
    {
        IList<int> ints = new List<int>();
        for (int i = 0; i < 3000;i++ )
        {
            ints.Add(i);
        }
        StringBuilder idString = new StringBuilder();
        foreach (int id in ints)
        {
            idString.Append(id + ", ");
        }
    }
    [TestMethod()]
    public void LINQ_SELECT()
    {
        IList<int> ints = new List<int>();
        for (int i = 0; i < 3000; i++)
        {
            ints.Add(i);
        }
        string ids = ints.Select(query => query.ToString())
                         .Aggregate((a, b) => a + ", " + b);
    }
brichins
  • 3,825
  • 2
  • 39
  • 60
user337754
  • 221
  • 2
  • 2
21

I always use the extension method:

public static string JoinAsString<T>(this IEnumerable<T> input, string seperator)
{
    var ar = input.Select(i => i.ToString());
    return string.Join(seperator, ar);
}
Carlo V. Dango
  • 13,322
  • 16
  • 71
  • 114
Kieran Benton
  • 8,739
  • 12
  • 53
  • 77
  • 5
    `string.Join` in .net 4 can already take an `IEnumerable` for any arbitrary `T`. – recursive May 16 '12 at 18:49
  • 1
    FYI: merged from http://stackoverflow.com/questions/122670/what-is-the-linq-way-to-implode-join-a-string-array – Shog9 Dec 31 '13 at 16:37
13

By 'super-cool LINQ way' you might be talking about the way that LINQ makes functional programming a lot more palatable with the use of extension methods. I mean, the syntactic sugar that allows functions to be chained in a visually linear way (one after the other) instead of nesting (one inside the other). For example:

int totalEven = Enumerable.Sum(Enumerable.Where(myInts, i => i % 2 == 0));

can be written like this:

int totalEven = myInts.Where(i => i % 2 == 0).Sum();

You can see how the second example is easier to read. You can also see how more functions can be added with less of the indentation problems or the Lispy closing parens appearing at the end of the expression.

A lot of the other answers state that the String.Join is the way to go because it is the fastest or simplest to read. But if you take my interpretation of 'super-cool LINQ way' then the answer is to use String.Join but have it wrapped in a LINQ style extension method that will allow you to chain your functions in a visually pleasing way. So if you want to write sa.Concatenate(", ") you just need to create something like this:

public static class EnumerableStringExtensions
{
   public static string Concatenate(this IEnumerable<string> strings, string separator)
   {
      return String.Join(separator, strings);
   }
}

This will provide code that is as performant as the direct call (at least in terms of algorithm complexity) and in some cases may make the code more readable (depending on the context) especially if other code in the block is using the chained function style.

Patrick McDonald
  • 64,141
  • 14
  • 108
  • 120
tpower
  • 56,100
  • 19
  • 68
  • 100
5

Here it is using pure LINQ as a single expression:

static string StringJoin(string sep, IEnumerable<string> strings) {
  return strings
    .Skip(1)
    .Aggregate(
       new StringBuilder().Append(strings.FirstOrDefault() ?? ""), 
       (sb, x) => sb.Append(sep).Append(x));
}

And its pretty damn fast!

cdiggins
  • 17,602
  • 7
  • 105
  • 102
3

I'm going to cheat a little and throw out a new answer to this that seems to sum up the best of everything on here instead of sticking it inside of a comment.

So you can one line this:

List<string> strings = new List<string>() { "one", "two", "three" };

string concat = strings        
    .Aggregate(new StringBuilder("\a"), 
                    (current, next) => current.Append(", ").Append(next))
    .ToString()
    .Replace("\a, ",string.Empty); 

Edit: You'll either want to check for an empty enumerable first or add an .Replace("\a",string.Empty); to the end of the expression. Guess I might have been trying to get a little too smart.

The answer from @a.friend might be slightly more performant, I'm not sure what Replace does under the hood compared to Remove. The only other caveat if some reason you wanted to concat strings that ended in \a's you would lose your separators... I find that unlikely. If that is the case you do have other fancy characters to choose from.

onedaywhen
  • 55,269
  • 12
  • 100
  • 138
Chris Marisic
  • 32,487
  • 24
  • 164
  • 258
2

Lots of choices here. You can use LINQ and a StringBuilder so you get the performance too like so:

StringBuilder builder = new StringBuilder();
List<string> MyList = new List<string>() {"one","two","three"};

MyList.ForEach(w => builder.Append(builder.Length > 0 ? ", " + w : w));
return builder.ToString();
Kelly
  • 6,992
  • 12
  • 59
  • 76
  • It would be faster to not check the `builder.Length > 0` in the ForEach and by removing the first comma after the ForEach – Binoj Antony Jun 09 '10 at 05:02
2

You can combine LINQ and string.join() quite effectively. Here I am removing an item from a string. There are better ways of doing this too but here it is:

filterset = String.Join(",",
                        filterset.Split(',')
                                 .Where(f => mycomplicatedMatch(f,paramToMatch))
                       );
p.campbell
  • 98,673
  • 67
  • 256
  • 322
Andiih
  • 12,285
  • 10
  • 57
  • 88
  • 1
    FYI: merged from http://stackoverflow.com/questions/122670/what-is-the-linq-way-to-implode-join-a-string-array – Shog9 Dec 31 '13 at 16:37
1

I did the following quick and dirty when parsing an IIS log file using linq, it worked @ 1 million lines pretty well (15 seconds), although got an out of memory error when trying 2 millions lines.

    static void Main(string[] args)
    {

        Debug.WriteLine(DateTime.Now.ToString() + " entering main");

        // USED THIS DOS COMMAND TO GET ALL THE DAILY FILES INTO A SINGLE FILE: copy *.log target.log 
        string[] lines = File.ReadAllLines(@"C:\Log File Analysis\12-8 E5.log");

        Debug.WriteLine(lines.Count().ToString());

        string[] a = lines.Where(x => !x.StartsWith("#Software:") &&
                                      !x.StartsWith("#Version:") &&
                                      !x.StartsWith("#Date:") &&
                                      !x.StartsWith("#Fields:") &&
                                      !x.Contains("_vti_") &&
                                      !x.Contains("/c$") &&
                                      !x.Contains("/favicon.ico") &&
                                      !x.Contains("/ - 80")
                                 ).ToArray();

        Debug.WriteLine(a.Count().ToString());

        string[] b = a
                    .Select(l => l.Split(' '))
                    .Select(words => string.Join(",", words))
                    .ToArray()
                    ;

        System.IO.File.WriteAllLines(@"C:\Log File Analysis\12-8 E5.csv", b);

        Debug.WriteLine(DateTime.Now.ToString() + " leaving main");

    }

The real reason I used linq was for a Distinct() I neede previously:

string[] b = a
    .Select(l => l.Split(' '))
    .Where(l => l.Length > 11)
    .Select(words => string.Format("{0},{1}",
        words[6].ToUpper(), // virtual dir / service
        words[10]) // client ip
    ).Distinct().ToArray()
    ;
Andy S.
  • 51
  • 2
  • 1
    FYI: merged from http://stackoverflow.com/questions/122670/what-is-the-linq-way-to-implode-join-a-string-array – Shog9 Dec 31 '13 at 16:37
0

I blogged about this a while ago, what I did seams to be exactly what you're looking for:

http://ondevelopment.blogspot.com/2009/02/string-concatenation-made-easy.html

In the blog post describe how to implement extension methods that works on IEnumerable and are named Concatenate, this will let you write things like:

var sequence = new string[] { "foo", "bar" };
string result = sequence.Concatenate();

Or more elaborate things like:

var methodNames = typeof(IFoo).GetMethods().Select(x => x.Name);
string result = methodNames.Concatenate(", ");
Patrik Hägne
  • 16,751
  • 5
  • 52
  • 60
  • 1
    FYI: merged from http://stackoverflow.com/questions/122670/what-is-the-linq-way-to-implode-join-a-string-array – Shog9 Dec 31 '13 at 16:37
0

FWIW I benchmarked string.Join vs .Aggregate on a string array of 15 strings using BDN:

Method Mean Error StdDev Gen0 Allocated
String_Join 92.99 ns 9.905 ns 0.543 ns 0.0560 352 B
LING_Aggregate 406.00 ns 74.662 ns 4.092 ns 0.4640 2912 B

The gap increases with bigger arrays

Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149