[c#] Creating a comma separated list from IList<string> or IEnumerable<string>

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.

This question is related to c# string

The answer is


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()));

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


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

string commaSeparatedList = input.Aggregate((a, x) => a + ", " + x)

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.


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)}";

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


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

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?


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)

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.


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;
    }

Here's another extension method:

    public static string Join(this IEnumerable<string> source, string separator)
    {
        return string.Join(separator, source);
    }

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.


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


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

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() );

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();
}

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

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;
    }

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

Hopefully this is the simplest way

 string Commaseplist;
 string[] itemList = { "Test1", "Test2", "Test3" };
 Commaseplist = string.join(",",itemList);
 Console.WriteLine(Commaseplist); //Outputs Test1,Test2,Test3

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

   var stringArray = stringList.ToArray();