tags:

views:

7211

answers:

16

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.

+36  A: 

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.

Jon Skeet
Seriously Jon, did you really have to wait until I posted the question to answer it? Surely you could have just telepathically answered me before I even asked couldn't you?Seriously though... good answer. I missed the addition of the extension method in 3.5 which makes it a snap.
Daniel Fortunov
I answered it a while ago. I just undeleted the answer when you got round to asking the question. Try to keep up ;)
Jon Skeet
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?
Eric
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?
Jon Skeet
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.
Eric
That's it. Every answer leads to Jon Skeet. I am just going to var PurchaseBooks = AmazonContainer.Where(p => p.Author == "Jon Skeet").Select();
Dr. Zim
A: 

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

JoshJordan
+1  A: 

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
Vikram
+9  A: 

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

string commaSeparatedList = input.Aggregate((a, x) => a + ", " + x)
Daniel Fortunov
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.
Jon Skeet
Still, it's the prettiest.
Merritt
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 :)
Matt Greer
I agree this is the prettiest and it solved my problem.. since I will never have a large input sequence, no worries :)
Avien
+1  A: 

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

   var stringArray = stringList.ToArray();
Richard
+1  A: 

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() );
Keith
I have an extension method that does almost exactly the same thing, very useful: http://stackoverflow.com/questions/696850/opportunities-to-use-func-to-improve-code-readability/709875#709875
LukeH
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.
Keith
+1  A: 

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

Brad
A: 

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;
    }
Paul Houle
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.
Daniel Fortunov
+1  A: 

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.

David Clarke
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.
David Clarke
This will fail with a zero-item IEnumerable source. sb.Length-- needs a bounds check.
James Dunne
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).
David Clarke
@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.
David Clarke
@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.
James Dunne
@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.
David Clarke
+5  A: 

FYI, the .NET 4.0 version of string.Join() has some extra overloads:

string.Join(string separator, IEnumerable<T> values)
Xavier Poinas
+1  A: 

Here's another extension method:

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

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.Remove(sb.Length - 1, 1);
    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.

James Dunne
A: 

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.

James Dunne
A: 

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?

Mike Kingscott
A: 

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();
        }
Dale King
A: 

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

Dan VanWinkle