views:

7234

answers:

7

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?

+7  A: 

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
Thanks, this helped me out by showing a different angle so I've upvoted it.
tags2k
Given the comments about performance, I should add that the example is from code that runs once when a dialog closes, and the list is unlikely to ever have more than about ten strings on it!
Daniel Earwicker
Any clue how to do this same task in Linq to Entities?
Binoj Antony
Excellent example. Thank you for putting this into a real world scenario. I had the same exact situation, with a property of an object that needed concating.
Jessy Houle
+30  A: 

Use aggregate queries like this:

string[] words = { "one", "two", "three" };
var res = words.Aggregate((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.

http://msdn.microsoft.com/en-us/library/bb386914.aspx

Because this does not use a StringBuilder it will have horrible performance for very long sequences.

smink
Note that this doesn't use a StringBuilder, so will have horrible performance for very long sequences.
Jon Skeet
True Jon Skeet. Added disclaimer at the end of the answer.
smink
Note that this fails if words has no elements inside it.
chocojosh
+1  A: 

There are various alternative answers at this previous question - which admittedly was targeting an integer array as the source, but received generalised answers.

Jon Skeet
+17  A: 
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);
David B
OK, so the solution doesn't use Linq, but it seems to work pretty well to me
Mat Roberts
ToArray is linq :)
David B
+1  A: 

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
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
+4  A: 

quick performance data for the stingbuilder vs Select case over 3000 elements:

unit test Duration (seconds) LINQ_SELECT 00:00:01.8012535
LINQ_StringBuilder 00:00:00.0036644

    [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);
    }
+4  A: 

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.)

a.friend
@guest - +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
You would save precious clock cycles by not checking the `if (length > 0)` in the linq and by taking it out.
Binoj Antony