Creating a comma separated list from IList<string> or IEnumerable<string>
Asked Answered
S

24

1061

What is the cleanest way to create a comma-separated list of string values from an IList<string> or IEnumerable<string>?

String.Join(...) operates on a string[] so can be cumbersome to work with when types such as IList<string> or IEnumerable<string> cannot easily be converted into a string array.

Sleuthhound answered 28/4, 2009 at 19:15 Comment(2)
Oh... whoops. I missed the addition of the ToArray extension method in 3.5: public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source)Sleuthhound
If you've come to this question looking for a means of writing CSV, it's worth remembering that simply inserting commas between items is insufficient and will cause failure in the case of quotes and commas in the source data.Dyal
M
1788

.NET 4+

IList<string> strings = new List<string>{"1","2","testing"};
string joined = string.Join(",", strings);

Detail & Pre .Net 4.0 Solutions

IEnumerable<string> can be converted into a string array very easily with LINQ (.NET 3.5):

IEnumerable<string> strings = ...;
string[] array = strings.ToArray();

It's easy enough to write the equivalent helper method if you need to:

public static T[] ToArray(IEnumerable<T> source)
{
    return new List<T>(source).ToArray();
}

Then call it like this:

IEnumerable<string> strings = ...;
string[] array = Helpers.ToArray(strings);

You can then call string.Join. Of course, you don't have to use a helper method:

// C# 3 and .NET 3.5 way:
string joined = string.Join(",", strings.ToArray());
// C# 2 and .NET 2.0 way:
string joined = string.Join(",", new List<string>(strings).ToArray());

The latter is a bit of a mouthful though :)

This is likely to be the simplest way to do it, and quite performant as well - there are other questions about exactly what the performance is like, including (but not limited to) this one.

As of .NET 4.0, there are more overloads available in string.Join, so you can actually just write:

string joined = string.Join(",", strings);

Much simpler :)

Mostly answered 28/4, 2009 at 19:17 Comment(6)
The helper method involves creating two unnecessary lists. Is this really the best way to solve the problem? Why not concatenate it yourself in a foreach loop?Baca
The helper method only creates one list and one array. The point is that the result needs to be an array, not a list... and you need to know the size of an array before you start. Best practice says you shouldn't enumerate a source more than once in LINQ unless you have to - it might be doing all kinds of nasty stuff. So, you're left with reading into buffers and resizing as you go - which is exactly what List<T> does. Why reinvent the wheel?Mostly
No, the point is that the result needs to be a concatenated string. There's no need to create a new list or a new array to achieve this goal. This kind of .NET mentality makes me sad.Baca
That's it. Every answer leads to Jon Skeet. I am just going to var PurchaseBooks = AmazonContainer.Where(p => p.Author == "Jon Skeet").Select();Pour
Thanks! This helped me too. @JonSkeet Is it wrong to have gone this far with one line (collecting int IDs)? string myStr = string.Join(",", (from a in foo select a.someInt.ToString()).ToList());Husted
@codeMonkey0110: Well there's no point in having a query expression there, or calling ToList. It's fine to use string myStr = string.Join(",", foo.Select(a => a.someInt.ToString())) though.Mostly
K
188

FYI, the .NET 4.0 version of string.Join() has some extra overloads, that work with IEnumerable instead of just arrays, including one that can deal with any type T:

public static string Join(string separator, IEnumerable<string> values)
public static string Join<T>(string separator, IEnumerable<T> values)
Kodiak answered 18/6, 2010 at 2:37 Comment(4)
This will call the T.ToString() method ?Sanguineous
Was just about to comment this on Jon's answer. Thanks for mentioning.Commentative
Anyway to do this on a property of an object? (Ex: IEnumerable<Employee> and the Employee object has a string .SSN property on it, and getting a comma separated list of SSN's.)Creek
You have to select the string first, though you could create an extension method that does that. str = emps.Select(e => e.SSN).Join(",")Kodiak
S
74

The easiest way I can see to do this is using the LINQ Aggregate method:

string commaSeparatedList = input.Aggregate((a, x) => a + ", " + x)
Sleuthhound answered 28/4, 2009 at 19:18 Comment(7)
That's not only more complicated (IMO) than ToArray + Join, it's also somewhat inefficient - with a large input sequence, that's going to start performing very badly.Mostly
Still, it's the prettiest.Sackey
You can feed Aggregate a StringBuilder seed, then your Aggregate Func becomes Func<StringBuilder,string,StringBuider>. Then just call ToString() on the returned StringBuilder. It's of course not as pretty though :)Woke
This is the clearest way of doing what the question asked for IMHO.Slave
Beware that input.Count should be more than 1.Laforge
@Laforge is right! Beware. To guard against the null/count issue try this: string commaSeparatedList = input == null || !input.Any() ? "" : input.Aggregate((a, x) => a + ", " + x); Note that it does work with Count == 1Citreous
The time complexity of this "solution" is quadratic. I would mark it as a bug if I saw code like this.Cabinet
R
36

I think that the cleanest way to create a comma-separated list of string values is simply:

string.Join<string>(",", stringEnumerable);

Here is a full example:

IEnumerable<string> stringEnumerable= new List<string>();
stringList.Add("Comma");
stringList.Add("Separated");

string.Join<string>(",", stringEnumerable);

There is no need to make a helper function, this is built into .NET 4.0 and above.

Rudderpost answered 5/8, 2010 at 19:52 Comment(2)
Note that this is applicable starting with .NET 4 (as Xavier pointed out in his answer).Slave
From the standpoint of .NET 4 newbie with less than a month's experience this answer was a nice combination of correctness and succinctnessSomatoplasm
S
24

If the strings you want to join are in List of Objects, then you can do something like this too:

var studentNames = string.Join(", ", students.Select(x => x.name));
Sturrock answered 23/2, 2020 at 22:21 Comment(2)
Can we somehow skip adding the empty/null values to the studentNames? Because currently, x.name takes all values even if the x.name has null valuesOverstrung
@Overstrung you can try this: var studentNames = string.Join(", ", students.Where(x => !string.IsNullOrEmpty(x)).Select(x => x.name));Sturrock
P
19

Comparing by performance the winner is "Loop it, sb.Append it, and do back step". Actually "enumerable and manual move next" is the same good (consider stddev).

BenchmarkDotNet=v0.10.5, OS=Windows 10.0.14393
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233539 Hz, Resolution=309.2587 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0
  Core   : .NET Core 4.6.25009.03, 64bit RyuJIT


                Method |  Job | Runtime |     Mean |     Error |    StdDev |      Min |      Max |   Median | Rank |  Gen 0 | Allocated |
---------------------- |----- |-------- |---------:|----------:|----------:|---------:|---------:|---------:|-----:|-------:|----------:|
            StringJoin |  Clr |     Clr | 28.24 us | 0.4381 us | 0.3659 us | 27.68 us | 29.10 us | 28.21 us |    8 | 4.9969 |   16.3 kB |
 SeparatorSubstitution |  Clr |     Clr | 17.90 us | 0.2900 us | 0.2712 us | 17.55 us | 18.37 us | 17.80 us |    6 | 4.9296 |  16.27 kB |
     SeparatorStepBack |  Clr |     Clr | 16.81 us | 0.1289 us | 0.1206 us | 16.64 us | 17.05 us | 16.81 us |    2 | 4.9459 |  16.27 kB |
            Enumerable |  Clr |     Clr | 17.27 us | 0.0736 us | 0.0615 us | 17.17 us | 17.36 us | 17.29 us |    4 | 4.9377 |  16.27 kB |
            StringJoin | Core |    Core | 27.51 us | 0.5340 us | 0.4995 us | 26.80 us | 28.25 us | 27.51 us |    7 | 5.0296 |  16.26 kB |
 SeparatorSubstitution | Core |    Core | 17.37 us | 0.1664 us | 0.1557 us | 17.15 us | 17.68 us | 17.39 us |    5 | 4.9622 |  16.22 kB |
     SeparatorStepBack | Core |    Core | 15.65 us | 0.1545 us | 0.1290 us | 15.45 us | 15.82 us | 15.66 us |    1 | 4.9622 |  16.22 kB |
            Enumerable | Core |    Core | 17.00 us | 0.0905 us | 0.0654 us | 16.93 us | 17.12 us | 16.98 us |    3 | 4.9622 |  16.22 kB |

Code:

public class BenchmarkStringUnion
{
    List<string> testData = new List<string>();
    public BenchmarkStringUnion()
    {
        for(int i=0;i<1000;i++)
        {
            testData.Add(i.ToString());
        }
    }
    [Benchmark]
    public string StringJoin()
    {
        var text = string.Join<string>(",", testData);
        return text;
    }
    [Benchmark]
    public string SeparatorSubstitution()
    {
        var sb = new StringBuilder();
        var separator = String.Empty;
        foreach (var value in testData)
        {
            sb.Append(separator).Append(value);
            separator = ",";
        }
        return sb.ToString();
    }

    [Benchmark]
    public string SeparatorStepBack()
    {
        var sb = new StringBuilder();
        foreach (var item in testData)
            sb.Append(item).Append(',');
        if (sb.Length>=1) 
            sb.Length--;
        return sb.ToString();
    }

    [Benchmark]
    public string Enumerable()
    {
        var sb = new StringBuilder();
        var e = testData.GetEnumerator();
        bool  moveNext = e.MoveNext();
        while (moveNext)
        {
            sb.Append(e.Current);
            moveNext = e.MoveNext();
            if (moveNext) 
                sb.Append(",");
        }
        return sb.ToString();
    }
}

https://github.com/dotnet/BenchmarkDotNet was used

Phosphoresce answered 4/5, 2017 at 13:5 Comment(0)
D
14

Since I reached here while searching to join on a specific property of a list of objects (and not the ToString() of it) here's an addition to the accepted answer:

var commaDelimited = string.Join(",", students.Where(i => i.Category == studentCategory)
                                 .Select(i => i.FirstName));
Dehiscence answered 20/8, 2016 at 6:34 Comment(1)
Every single time I need to do this, I think "I really should take a few minutes to figure out how to do this in one line using string.Join(...)" but then I end up just foreach-ing it and moving on. Thanks for posting this! :)Thighbone
R
9

Here's another extension method:

    public static string Join(this IEnumerable<string> source, string separator)
    {
        return string.Join(separator, source);
    }
Renaterenato answered 18/6, 2010 at 13:38 Comment(0)
I
8

Arriving a little late to this discussion but this is my contribution fwiw. I have an IList<Guid> OrderIds to be converted to a CSV string but following is generic and works unmodified with other types:

string csv = OrderIds.Aggregate(new StringBuilder(),
             (sb, v) => sb.Append(v).Append(","),
             sb => {if (0 < sb.Length) sb.Length--; return sb.ToString();});

Short and sweet, uses StringBuilder for constructing new string, shrinks StringBuilder length by one to remove last comma and returns CSV string.

I've updated this to use multiple Append()'s to add string + comma. From James' feedback I used Reflector to have a look at StringBuilder.AppendFormat(). Turns out AppendFormat() uses a StringBuilder to construct the format string which makes it less efficient in this context than just using multiple Appends()'s.

Iodic answered 18/6, 2010 at 2:31 Comment(6)
Gazumped, thanks Xavier I wasn't aware of that update in .Net4. The project I'm working on hasn't made the leap yet so I'll keep using my now pedestrian example in the meantime.Iodic
This will fail with a zero-item IEnumerable source. sb.Length-- needs a bounds check.Kailey
Nice catch thanks James, in the context where I'm using this I'm "guaranteed" to have at least one OrderId. I've updated both the example and my own code to include the bounds check (just to be sure).Iodic
@James I think calling sb.Length-- a hack is a little harsh. Effectively I'm just avoiding your "if (notdone)" test until the end rather than doing it in each iteration.Iodic
@david.clarke No I don't think it's too harsh; a StringBuilder is meant to be used most optimally by just doing Append() operations until your string is complete. I'm not sure why you seem to imply you are gaining anything here by avoiding the if (notdone) test until the end. The notdone variable stores the last result of MoveNext() which is called anyway on the IEnumerable interface in both of these solutions.Kailey
@James my point is there is often more than one correct answer to questions asked here and referring to one as a "hack" implies it is incorrect which I would dispute. For the small number of guids I'm concatenating Daniel's answer above would probably be perfectly adequate and it's certainly more succinct/readable then my answer. I am using this in only one place in my code and I will only ever use a comma as a delimiter. YAGNI says don't build something you aren't going to need. DRY is applicable if I needed to do it more than once at which point I would create an exension method. HTH.Iodic
L
8

Specific need when we should surround by ', by ex:

        string[] arr = { "jj", "laa", "123" };
        List<string> myList = arr.ToList();

        // 'jj', 'laa', '123'
        Console.WriteLine(string.Join(", ",
            myList.ConvertAll(m =>
                string.Format("'{0}'", m)).ToArray()));
Larimor answered 19/12, 2013 at 16:53 Comment(0)
K
7

Something a bit fugly, but it works:

string divisionsCSV = String.Join(",", ((List<IDivisionView>)divisions).ConvertAll<string>(d => d.DivisionID.ToString("b")).ToArray());

Gives you a CSV from a List after you give it the convertor (in this case d => d.DivisionID.ToString("b")).

Hacky but works - could be made into an extension method perhaps?

Kukri answered 5/7, 2010 at 15:4 Comment(0)
E
7

Here's the way I did it, using the way I have done it in other languages:

private string ToStringList<T>(IEnumerable<T> list, string delimiter)
{
  var sb = new StringBuilder();
  string separator = String.Empty;
  foreach (T value in list)
  {
    sb.Append(separator).Append(value);
    separator = delimiter;
  }
  return sb.ToString();
}
Eventempered answered 4/8, 2010 at 2:25 Comment(0)
A
4

We have a utility function, something like this:

public static string Join<T>( string delimiter, 
    IEnumerable<T> collection, Func<T, string> convert )
{
    return string.Join( delimiter, 
        collection.Select( convert ).ToArray() );
}

Which can be used for joining lots of collections easily:

int[] ids = {1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233};

string csv = StringUtility.Join(",", ids, i => i.ToString() );

Note that we have the collection param before the lambda because intellisense then picks up the collection type.

If you already have an enumeration of strings all you need to do is the ToArray:

string csv = string.Join( ",", myStrings.ToArray() );
Architecture answered 28/4, 2009 at 19:33 Comment(2)
I have an extension method that does almost exactly the same thing, very useful: #697350Fibula
Yeah, you could write this as a .ToDelimitedString extension method easily enough. I'd go with my single line string.Join one rather than using a StringBuilder an trimming the last char.Architecture
M
3

I just solved this issue before happening across this article. My solution goes something like below :

   private static string GetSeparator<T>(IList<T> list, T item)
   {
       return (list.IndexOf(item) == list.Count - 1) ? "" : ", ";
   }

Called like:

List<thing> myThings;
string tidyString;

foreach (var thing in myThings)
{
     tidyString += string.format("Thing {0} is a {1}", thing.id, thing.name) + GetSeparator(myThings, thing);
}

I could also have just as easily expressed as such and would have also been more efficient:

string.Join(“,”, myThings.Select(t => string.format(“Thing {0} is a {1}”, t.id, t.name)); 
Mecham answered 28/4, 2009 at 19:16 Comment(0)
S
3

you can convert the IList to an array using ToArray and then run a string.join command on the array.

Dim strs As New List(Of String)
Dim arr As Array
arr = strs.ToArray
Sanitarium answered 28/4, 2009 at 19:18 Comment(0)
C
3

They can be easily converted to an array using the Linq extensions in .NET 3.5.

   var stringArray = stringList.ToArray();
Cincinnatus answered 28/4, 2009 at 19:19 Comment(0)
C
3

You could also use something like the following after you have it converted to an array using one of the of methods listed by others:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Configuration;

namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            CommaDelimitedStringCollection commaStr = new CommaDelimitedStringCollection();
            string[] itemList = { "Test1", "Test2", "Test3" };
            commaStr.AddRange(itemList);
            Console.WriteLine(commaStr.ToString()); //Outputs Test1,Test2,Test3
            Console.ReadLine();
        }
    }
}

Edit: Here is another example

Cheyney answered 23/7, 2009 at 19:36 Comment(0)
K
3

My answer is like above Aggregate solution but should be less call-stack heavy since there are no explicit delegate calls:

public static string ToCommaDelimitedString<T>(this IEnumerable<T> items)
{
    StringBuilder sb = new StringBuilder();
    foreach (var item in items)
    {
        sb.Append(item.ToString());
        sb.Append(',');
    }
    if (sb.Length >= 1) sb.Length--;
    return sb.ToString();
}

Of course, one can extend the signature to be delimiter-independent. I'm really not a fan of the sb.Remove() call and I'd like to refactor it to be a straight-up while-loop over an IEnumerable and use MoveNext() to determine whether or not to write a comma. I'll fiddle around and post that solution if I come upon it.


Here's what I wanted initially:

public static string ToDelimitedString<T>(this IEnumerable<T> source, string delimiter, Func<T, string> converter)
{
    StringBuilder sb = new StringBuilder();
    var en = source.GetEnumerator();
    bool notdone = en.MoveNext();
    while (notdone)
    {
        sb.Append(converter(en.Current));
        notdone = en.MoveNext();
        if (notdone) sb.Append(delimiter);
    }
    return sb.ToString();
}

No temporary array or list storage required and no StringBuilder Remove() or Length-- hack required.

In my framework library I made a few variations on this method signature, every combination of including the delimiter and the converter parameters with usage of "," and x.ToString() as defaults, respectively.

Kailey answered 22/6, 2010 at 16:40 Comment(0)
A
3

Hopefully this is the simplest way

 string Commaseplist;
 string[] itemList = { "Test1", "Test2", "Test3" };
 Commaseplist = string.join(",",itemList);
 Console.WriteLine(Commaseplist); //Outputs Test1,Test2,Test3
Affettuoso answered 12/4, 2013 at 10:11 Comment(0)
H
3

I came over this discussion while searching for a good C# method to join strings like it is done with the MySql method CONCAT_WS(). This method differs from the string.Join() method in that it does not add the separator sign if strings are NULL or empty.

CONCAT_WS(', ',tbl.Lastname,tbl.Firstname)

will return only Lastname if firstname is empty, whilst

string.Join(", ", strLastname, strFirstname)

will return strLastname + ", " in the same case.

Wanting the first behavior, I wrote the following methods:

    public static string JoinStringsIfNotNullOrEmpty(string strSeparator, string strA, string strB, string strC = "")
    {
        return JoinStringsIfNotNullOrEmpty(strSeparator, new[] {strA, strB, strC});
    }

    public static string JoinStringsIfNotNullOrEmpty(string strSeparator, string[] arrayStrings)
    {
        if (strSeparator == null)
            strSeparator = "";
        if (arrayStrings == null)
            return "";
        string strRetVal = arrayStrings.Where(str => !string.IsNullOrEmpty(str)).Aggregate("", (current, str) => current + (str + strSeparator));
        int trimEndStartIndex = strRetVal.Length - strSeparator.Length;
        if (trimEndStartIndex>0)
            strRetVal = strRetVal.Remove(trimEndStartIndex);
        return strRetVal;
    }
Happiness answered 25/11, 2015 at 16:21 Comment(1)
Alternative: string separator = ", "; string strA = "High"; string strB = ""; string strC = "Five"; string strD = null; var myStrings = new List<string> { strA, strB, strC, strD }; IEnumerable<string> myValidStrings = myStrings.Where(item => !string.IsNullOrWhiteSpace(item)); return string.Join(separator, myValidStrings );Priscilapriscilla
C
2

You can use .ToArray() on Lists and IEnumerables, and then use String.Join() as you wanted.

Custombuilt answered 28/4, 2009 at 19:18 Comment(0)
B
2

I wrote a few extension methods to do it in a way that's efficient:

    public static string JoinWithDelimiter(this IEnumerable<String> that, string delim) {
        var sb = new StringBuilder();
        foreach (var s in that) {
            sb.AppendToList(s,delim);
        }

        return sb.ToString();
    }

This depends on

    public static string AppendToList(this String s, string item, string delim) {
        if (s.Length == 0) {
            return item;
        }

        return s+delim+item;
    }
Batter answered 25/11, 2009 at 18:44 Comment(1)
Using the + operator to concatenate strings is not great because it will cause a new string to be allocated each time. Further more, although the StringBuilder can be implicitly cast to a string, doing so frequently (every iteration of your loop) would largely defeat the purpose of having a string builder.Sleuthhound
D
1

To create a comma separated list from an IList<string> or IEnumerable<string>, besides using string.Join() you can use the StringBuilder.AppendJoin method:

new StringBuilder().AppendJoin(", ", itemList).ToString();

or

$"{new StringBuilder().AppendJoin(", ", itemList)}";
Dx answered 27/6, 2020 at 15:0 Comment(0)
O
1

I know it's bit late to answer this question but it might be helpful who land up here searching answer for this issue.

You could do something like below:

var finalString = String.Join(",", ExampleArrayOfObjects.Where(newList => !String.IsNullOrEmpty(newList.TestParameter)).Select(newList => newList.TestParameter));

Using ExampleArrayOfObjects.Where we will create new objectlist with non-empty values

And then further using .Select over that new objectlist to join with "," as a separator and make final string.

Overstrung answered 5/8, 2022 at 16:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.