Join collection of objects into comma-separated string
Asked Answered
N

11

30

In many places in our code we have collections of objects, from which we need to create a comma-separated list. The type of collection varies: it may be a DataTable from which we need a certain column, or a List<Customer>, etc.

Now we loop through the collection and use string concatenation, for example:

string text = "";
string separator = "";
foreach (DataRow row in table.Rows)
{
    text += separator + row["title"];
    separator = ", ";
}

Is there a better pattern for this? Ideally I would like an approach we could reuse by just sending in a function to get the right field/property/column from each object.

Nedranedrah answered 1/12, 2008 at 10:45 Comment(0)
M
10
// using System.Collections;
// using System.Collections.Generic;
// using System.Linq

public delegate string Indexer<T>(T obj);

public static string concatenate<T>(IEnumerable<T> collection, Indexer<T> indexer, char separator)
{
    StringBuilder sb = new StringBuilder();
    foreach (T t in collection) sb.Append(indexer(t)).Append(separator);
    return sb.Remove(sb.Length - 1, 1).ToString();
}

// version for non-generic collections
public static string concatenate<T>(IEnumerable collection, Indexer<T> indexer, char separator)
{
    StringBuilder sb = new StringBuilder();
    foreach (object t in collection) sb.Append(indexer((T)t)).Append(separator);
    return sb.Remove(sb.Length - 1, 1).ToString();
}

// example 1: simple int list
string getAllInts(IEnumerable<int> listOfInts)
{
    return concatenate<int>(listOfInts, Convert.ToString, ',');
}

// example 2: DataTable.Rows
string getTitle(DataRow row) { return row["title"].ToString(); }
string getAllTitles(DataTable table)
{
    return concatenate<DataRow>(table.Rows, getTitle, '\n');
}

// example 3: DataTable.Rows without Indexer function
string getAllTitles(DataTable table)
{
    return concatenate<DataRow>(table.Rows, r => r["title"].ToString(), '\n');
}
Mahaffey answered 1/12, 2008 at 11:11 Comment(3)
This does exactly what I wanted to achieve, thank you! Although I find lambda expressions more readable (like in Matt's solution).Nedranedrah
I agree that lambda expressions are much more readable, but I have only used .NET 2.0 so far. I hope to learn them soon.Mahaffey
You might want to change the separator to be a string instead of a char, but then don't forget to change the "sb.Remove" call to be "sb.Remove(sb.Length - separator.Length, separator.Length)"Mahaffey
E
99
string.Join(", ", Array.ConvertAll(somelist.ToArray(), i => i.ToString()))
Extended answered 1/12, 2008 at 11:20 Comment(9)
nice and clean. What if someList is declared as IList? It lacks the ToArray() method.Tasha
@CristiDiaconescu: string.Join(", ", somelist.Select(t => t.ToString()).ToArray())Mireielle
I get System.Array does not contain a definition for 'Convert'Ruttger
@JoshStodola: Say what? You trying to compile with the JVM? ;p It has been there since 1.0.Extended
@Extended Wrong. It is not in 1.1, it's not in 2.0, and it's not in 3.5. I think you meant ConvertAllRuttger
@JoshStodola: Good point ;p Intellisense makes you lazy I guess.Extended
What if the somelist.ToArray() contains a string that already has a "," in it? Does it not break this? How do you fix it?Rachaba
@Hunter: That is irrelevant.Extended
Another option for .Net 3.5 and up when you have a Generic List of StringBuilder for example and you want to use Lambada expression: codestring.Join(",", (from s in queries select s.ToString()).ToArray());codeWarnock
S
12
static string ToCsv<T>(IEnumerable<T> things, Func<T, string> toStringMethod)
{
    StringBuilder sb = new StringBuilder();

    foreach (T thing in things)
        sb.Append(toStringMethod(thing)).Append(',');

    return sb.ToString(0, sb.Length - 1); //remove trailing ,
}

Use like this:

DataTable dt = ...; //datatable with some data
Console.WriteLine(ToCsv(dt.Rows, row => row["ColName"]));

or:

List<Customer> customers = ...; //assume Customer has a Name property
Console.WriteLine(ToCsv(customers, c => c.Name));

I don't have a compiler to hand but in theory it should work. And as everyone knows, in theory, practice and theory are the same. In practice, they're not.

Sire answered 1/12, 2008 at 11:0 Comment(2)
This is what I was looking for, thank you! Theory is not the same as practice here, you need two overloads for the method (one for generics and one for non-generics), like in Hosam Aly's answer. Your answer is a lot easier to read, though.Nedranedrah
This is the best answer, especially if you follow through with BigBlondViking's improvement in making it an extension method. Much cleaner and works for complex objects.Brahmanism
P
11

I found string.Join and lambda Select<Func<>> helps to write minimum code.

List<string> fruits = new List<string>();
fruits.Add("Mango");
fruits.Add("Banana");
fruits.Add("Papaya");

string commaSepFruits = string.Join(",", fruits.Select(f => "'" + f + "'"));
Console.WriteLine(commaSepFruits);

List<int> ids = new List<int>();
ids.Add(1001);
ids.Add(1002);
ids.Add(1003);

string commaSepIds = string.Join(",", ids);
Console.WriteLine(commaSepIds);

List<Customer> customers = new List<Customer>();
customers.Add(new Customer { Id = 10001, Name = "John" });
customers.Add(new Customer { Id = 10002, Name = "Robert" });
customers.Add(new Customer { Id = 10002, Name = "Ryan" });

string commaSepCustIds = string.Join(", ", customers.Select(cust => cust.Id));
string commaSepCustNames = string.Join(", ", customers.Select(cust => "'" + cust.Name + "'"));

Console.WriteLine(commaSepCustIds);
Console.WriteLine(commaSepCustNames);

Console.ReadLine();
Parsifal answered 4/3, 2011 at 15:33 Comment(0)
M
10
// using System.Collections;
// using System.Collections.Generic;
// using System.Linq

public delegate string Indexer<T>(T obj);

public static string concatenate<T>(IEnumerable<T> collection, Indexer<T> indexer, char separator)
{
    StringBuilder sb = new StringBuilder();
    foreach (T t in collection) sb.Append(indexer(t)).Append(separator);
    return sb.Remove(sb.Length - 1, 1).ToString();
}

// version for non-generic collections
public static string concatenate<T>(IEnumerable collection, Indexer<T> indexer, char separator)
{
    StringBuilder sb = new StringBuilder();
    foreach (object t in collection) sb.Append(indexer((T)t)).Append(separator);
    return sb.Remove(sb.Length - 1, 1).ToString();
}

// example 1: simple int list
string getAllInts(IEnumerable<int> listOfInts)
{
    return concatenate<int>(listOfInts, Convert.ToString, ',');
}

// example 2: DataTable.Rows
string getTitle(DataRow row) { return row["title"].ToString(); }
string getAllTitles(DataTable table)
{
    return concatenate<DataRow>(table.Rows, getTitle, '\n');
}

// example 3: DataTable.Rows without Indexer function
string getAllTitles(DataTable table)
{
    return concatenate<DataRow>(table.Rows, r => r["title"].ToString(), '\n');
}
Mahaffey answered 1/12, 2008 at 11:11 Comment(3)
This does exactly what I wanted to achieve, thank you! Although I find lambda expressions more readable (like in Matt's solution).Nedranedrah
I agree that lambda expressions are much more readable, but I have only used .NET 2.0 so far. I hope to learn them soon.Mahaffey
You might want to change the separator to be a string instead of a char, but then don't forget to change the "sb.Remove" call to be "sb.Remove(sb.Length - separator.Length, separator.Length)"Mahaffey
T
7

In .NET 4 you can just do string.Join(", ", table.Rows.Select(r => r["title"]))

Tailband answered 18/4, 2010 at 6:13 Comment(0)
L
6

You could write a function that transforms a IEnumerable<string> into a comma-separated string:

public string Concat(IEnumerable<string> stringList)
{
    StringBuilder textBuilder = new StringBuilder();
    string separator = String.Empty;
    foreach(string item in stringList)
    {
        textBuilder.Append(separator);
        textBuilder.Append(item);
        separator = ", ";
    }
    return textBuilder.ToString();
}

You can then use LINQ to query your collection/dataset/etc to provide the stringList.

Lentha answered 1/12, 2008 at 11:2 Comment(2)
While Join is probably the way that I would normally go, I always like to see this idiom of using an empty separator in the first iteration and the desired separator in subsequent. It looks cleaner than always adding the separator and then having to strip it off afterwards.Horizon
Just noticed I was adding separators and items in the wrong order... silly me! Fixed it :-)Lentha
N
2

As an aside: The first modification I would make is to use the StringBuilder Class instead of just a String - it'll save resources for you.

Neonate answered 1/12, 2008 at 10:53 Comment(0)
C
2

I love Matt Howells answer in this post:

I had to make it into an extension:

public static string ToCsv<T>(this IEnumerable<T> things, Func<T, string> toStringMethod)

Usage (I am getting all the emails and turning them into a CSV string for emails):

var list = Session.Find("from User u where u.IsActive = true").Cast<User>();

return list.ToCsv(i => i.Email);
Cuyler answered 6/7, 2009 at 17:4 Comment(1)
This is exactly what I did. You should also use return sb.ToString().TrimEnd(','); to remove the trailing comma. It is clearer in function and if there are no elements in the enumerable you pass it, his function would throw a cannot-be-less-than-zero-length error since 0 - 1 = -1.Brahmanism
H
2

For collections you can use this method as well, for example:

string.Join(", ", contactsCollection.Select(i => i.FirstName));

You can select any property that you want to separate.

Hali answered 26/5, 2018 at 23:45 Comment(0)
P
1
string strTest = "1,2,4,6";
string[] Nums = strTest.Split(',');
Console.Write(Nums.Aggregate<string>((first, second) => first + "," + second));
//OUTPUT:
//1,2,4,6
Purify answered 11/2, 2009 at 14:24 Comment(2)
Aggregate returns the same data type as the input values, i.e. as far as I can see I cannot aggregate a List<int> into a string.Nedranedrah
Maybe table.Rows.Select(r => r["title"].ToString()).Aggregate((a, b) => a + "," + b) then?Outgrowth
E
0

Here's my favorite answer adapted to the question, and corrected Convert to ConvertAll:

string text = string.Join(", ", Array.ConvertAll(table.Rows.ToArray(), i => i["title"]));
Expendable answered 11/5, 2012 at 13:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.