2008-12-01 14 views
25

En muchos lugares de nuestro código tenemos colecciones de objetos, de los cuales necesitamos crear una lista separada por comas. El tipo de recogida varía: puede ser un DataTable de la que necesitamos una cierta columna, o una lista <cliente>, etc.Unir la colección de objetos en una cadena separada por comas

Ahora bucle a través de la concatenación recopilación y el uso de cuerdas, por ejemplo:

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

¿Hay un mejor patrón para esto? Idealmente, me gustaría un enfoque que pudiéramos reutilizar simplemente enviando una función para obtener el campo/propiedad/columna correcto de cada objeto.

Respuesta

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'); 
} 
+1

¡Esto hace exactamente lo que yo quería lograr, gracias! Aunque encuentro que las expresiones lambda son más legibles (como en la solución de Matt). –

+1

Acepto que las expresiones lambda son mucho más legibles, pero hasta ahora solo he usado .NET 2.0. Espero aprenderlos pronto –

+1

Es posible que desee cambiar el separador para que sea una cadena en lugar de una char, pero no olvide cambiar la llamada "sb.Remove" para que sea "sb.Remove (sb.Length - separator.Length, separator.Length) " –

2

Como nota aparte: la primera modificación que haría sería usar el StringBuilder Class en lugar de solo un String: ahorrará recursos para usted.

11
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 , 
} 

uso como esto:

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

o:

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

no tengo un compilador a mano pero en teoría debería funcionar. Y como todos saben, en teoría, la práctica y la teoría son las mismas. En la práctica, no lo son.

+0

Esto es lo que estaba buscando, gracias! La teoría no es lo mismo que practicar aquí, necesita dos sobrecargas para el método (uno para genéricos y otro para no genéricos), como en la respuesta de Hosam Aly. Sin embargo, tu respuesta es mucho más fácil de leer. –

+0

Esta es la mejor respuesta, especialmente si cumple con la mejora de BigBlondViking al convertirlo en un método de extensión. Mucho más limpio y funciona para objetos complejos. – smdrager

6

Se podría escribir una función que transforma un IEnumerable en una cadena separada por comas

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

A continuación, puede utilizar LINQ para consultar su colección/base de datos/etc para proporcionar la StringList.

+0

Mientras Join es probablemente la forma en que normalmente iría, siempre me gusta ver este modismo de usar un separador vacío en la primera iteración y el separador deseado en la siguiente. Se ve más limpio que siempre agregar el separador y luego tener que quitarlo después. – wageoghe

+0

Acabo de notar que estaba agregando separadores y elementos en el orden incorrecto ... ¡tonto! Se corrigió :-) – Mendelt

88
string.Join(", ", Array.ConvertAll(somelist.ToArray(), i => i.ToString())) 
+1

agradable y limpio. ¿Qué ocurre si someList se declara como IList? Carece del método ToArray(). –

+4

@CristiDiaconescu: string.join ("", somelist.Select (t => t.ToString()) ToArray()) – cederlof

+0

obtengo 'System.array no contiene una definición para 'Convert'' –

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 
+0

Agregado devuelve el mismo tipo de datos que los valores de entrada, es decir, que por lo que puedo ver, no puedo agregar una lista en una cadena. –

+0

Quizás table.Rows.Select (r => r ["title"]. ToString()). Aggregate ((a, b) => a + "," + b) then? –

2

Me encanta Matt Howells answer en este post:

tuve que hacerlo en una extensión:

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

Uso: [Me estoy haciendo todo el correos electrónicos y convertirlos en una cadena de csv para correos electrónicos]:

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

return list.ToCsv(i => i.Email); 
+0

Esto es exactamente lo que hice. También debe usar return 'sb.ToString(). TrimEnd (',');' para eliminar la coma final. Tiene una función más clara y si no hay elementos en el enumerable que usted pase, su función arrojaría un error de "no puede ser menos que cero" desde 0 - 1 = -1. – smdrager

5

En .NET 4 se puede simplemente hacer string.Join(", ", table.Rows.Select(r => r["title"]))

10

encontré string.join y Lambda Seleccionar> ayuda para escribir código mínimo.

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

aquí está mi respuesta favorita adaptado a la pregunta, y corregido Convertir a ConvertAll:

string text = string.Join(", ", Array.ConvertAll(table.Rows.ToArray(), i => i["title"])); 
Cuestiones relacionadas