2009-12-11 17 views
108

Ho un elenco di valori interi (Elenco) e vorrei generare una stringa di valori delimitati da virgole. Ciò significa che tutti gli elementi nell'elenco vengono visualizzati in un elenco delimitato da una virgola.Conversione di un elenco generico in una stringa CSV

I miei pensieri ... 1. passare l'elenco a un metodo. 2. Utilizzare il stringbuilder per iterare l'elenco e aggiungere le virgole 3. Verificare l'ultimo carattere e se è una virgola, eliminarlo.

Quali sono i tuoi pensieri? È questo il modo migliore?

Come cambierebbe il mio codice se volessi gestire non solo interi (il mio piano corrente) ma stringhe, long, double, bool, ecc. Ecc. In futuro? Immagino che accetti un elenco di qualsiasi tipo.

risposta

189

È incredibile ciò che il Framework già fa per noi.

List<int> myValues; 
string csv = String.Join(",", myValues.Select(x => x.ToString()).ToArray()); 

Per il caso generale:

IEnumerable<T> myList; 
string csv = String.Join(",", myList.Select(x => x.ToString()).ToArray()); 

Come si può vedere, è efficace non è diverso. Fai attenzione che potresti dover effettivamente inserire x.ToString() tra virgolette (ad esempio, "\"" + x.ToString() + "\"") nel caso x.ToString() contenente virgole.

Per una lettura interessante di una leggera variante di questo: vedere Comma Quibbling sul blog di Eric Lippert.

Nota: questo è stato scritto prima che .NET 4.0 fosse rilasciato ufficialmente. Ora possiamo solo dire

IEnumerable<T> sequence; 
string csv = String.Join(",", sequence); 

utilizzando il sovraccarico String.Join<T>(string, IEnumerable<T>). Questo metodo proietterà automaticamente ogni elemento x a x.ToString().

+0

'Lista ' non ha metodo 'select' nel framework 3.5 a meno che non mi manca qualcosa. – ajeh

+2

@ajeh: Probabilmente manca un'istruzione 'using'. – jason

+0

Quale importazione specifica? – ajeh

7

È possibile utilizzare String.Join.

String.Join(
    ",", 
    Array.ConvertAll(
    list.ToArray(), 
    element => element.ToString() 
) 
); 
+0

Non c'è bisogno di specificare parametri di tipo generico nella chiamata a 'ConvertAll' qui - sia 'int' che' string' saranno dedotti. –

+0

Grazie per il suggerimento. –

+1

Invece di fare 'Array.ConvertAll (... 'puoi semplicemente fare' list.ConvertAll (e => e.ToString()). ToArray) ', solo meno digitando. – David

10

È possibile creare un metodo di estensione che è possibile chiamare in qualsiasi IEnumerable:

public static string JoinStrings<T>(
    this IEnumerable<T> values, string separator) 
{ 
    var stringValues = values.Select(item => 
     (item == null ? string.Empty : item.ToString())); 
    return string.Join(separator, stringValues.ToArray()); 
} 

allora si può solo chiamare il metodo della lista originale:

string commaSeparated = myList.JoinStrings(", "); 
10

a 3.5, ero ancora in grado di farlo. È molto più semplice e non ha bisogno di lambda.

String.Join(",", myList.ToArray<string>()); 
+0

Il metodo 'ToArray()' di 'Lista ' non può essere utilizzato con l'argomento type nel framework 3.5 a meno che non manchi qualcosa. – ajeh

3

Qualsiasi intervento soluzione solo se List una lista (stringa)

Se si dispone di un elenco generico dei propri oggetti come lista (di auto), dove un'auto ha proprietà n, si deve effettuare il ciclo della PropertiesInfo di ogni oggetto auto.

un'occhiata a: http://www.csharptocsharp.com/generate-csv-from-generic-list

+1

non puoi ignorare ToString della classe e utilizzare i metodi sopra riportati? –

5

Se qualsiasi organismo vuole convertire elenco di classe personalizzata oggetti invece di lista di stringhe poi eseguire l'override del metodo ToString della classe con la rappresentazione fila CSV della classe.

Public Class MyClass{ 
    public int Id{get;set;} 
    public String PropertyA{get;set;} 
    public override string ToString() 
    { 
    return this.Id+ "," + this.PropertyA; 
    } 
} 

Poi seguente codice può essere utilizzato per convertire questo elenco di classe in formato CSV con colonna di intestazione

string csvHeaderRow = String.Join(",", typeof(MyClass).GetProperties(BindingFlags.Public | BindingFlags.Instance).Select(x => x.Name).ToArray<string>()) + Environment.NewLine; 
string csv= csvHeaderRow + String.Join(Environment.NewLine, MyClass.Select(x => x.ToString()).ToArray()); 
4

Come il codice nel link data dal @Frank Create a CSV File from a .NET Generic List c'era un piccolo problema di finire ogni riga con un , Ho modificato il codice per sbarazzarmene. Spero che aiuti qualcuno.

/// <summary> 
/// Creates the CSV from a generic list. 
/// </summary>; 
/// <typeparam name="T"></typeparam>; 
/// <param name="list">The list.</param>; 
/// <param name="csvNameWithExt">Name of CSV (w/ path) w/ file ext.</param>; 
public static void CreateCSVFromGenericList<T>(List<T> list, string csvCompletePath) 
{ 
    if (list == null || list.Count == 0) return; 

    //get type from 0th member 
    Type t = list[0].GetType(); 
    string newLine = Environment.NewLine; 

    if (!Directory.Exists(Path.GetDirectoryName(csvCompletePath))) Directory.CreateDirectory(Path.GetDirectoryName(csvCompletePath)); 

    if (!File.Exists(csvCompletePath)) File.Create(csvCompletePath); 

    using (var sw = new StreamWriter(csvCompletePath)) 
    { 
     //make a new instance of the class name we figured out to get its props 
     object o = Activator.CreateInstance(t); 
     //gets all properties 
     PropertyInfo[] props = o.GetType().GetProperties(); 

     //foreach of the properties in class above, write out properties 
     //this is the header row 
     sw.Write(string.Join(",", props.Select(d => d.Name).ToArray()) + newLine); 

     //this acts as datarow 
     foreach (T item in list) 
     { 
      //this acts as datacolumn 
      var row = string.Join(",", props.Select(d => item.GetType() 
                  .GetProperty(d.Name) 
                  .GetValue(item, null) 
                  .ToString()) 
                .ToArray()); 
      sw.Write(row + newLine); 

     } 
    } 
} 
+0

Informazioni aggiuntive: il processo non può accedere al file 'c: \ temp \ matchingMainWav.csv' perché è utilizzato da un altro processo. esiste la cartella 'dev', ma non il file ... non sto usando quello giusto? –

+0

Il metodo File.Create crea il file e apre FileStream sul file. Quindi il tuo file è già aperto. Non hai proprio bisogno del metodo file.Create: – David

3

mi piace un bel metodo di estensione semplice

public static string ToCsv(this List<string> itemList) 
     { 
      return string.Join(",", itemList); 
     } 

Poi si può solo chiamare il metodo della lista originale:

string CsvString = myList.ToCsv(); 

pulito e più facile da leggere rispetto alcuni degli altri suggerimenti.

0

Il problema con String.Join è che non si sta gestendo il caso di una virgola già esistente nel valore. Quando esiste una virgola, si circonda il valore in Quotes e si sostituiscono tutte le Quotazioni esistenti con le doppie virgolette.

String.Join(",",{"this value has a , in it","This one doesn't", "This one , does"}); 

Vedi CSV Module

2

lo spiego in modo approfondito in questo post. Inserirò il codice qui con brevi descrizioni.

Ecco il metodo che crea la riga di intestazione. Utilizza i nomi delle proprietà come nomi di colonne.

private static void CreateHeader<T>(List<T> list, StreamWriter sw) 
    { 
     PropertyInfo[] properties = typeof(T).GetProperties(); 
     for (int i = 0; i < properties.Length - 1; i++) 
     { 
      sw.Write(properties[i].Name + ","); 
     } 
     var lastProp = properties[properties.Length - 1].Name; 
     sw.Write(lastProp + sw.NewLine); 
    } 

questo metodo crea tutte le righe di valore

private static void CreateRows<T>(List<T> list, StreamWriter sw) 
    { 
     foreach (var item in list) 
     { 
      PropertyInfo[] properties = typeof(T).GetProperties(); 
      for (int i = 0; i < properties.Length - 1; i++) 
      { 
       var prop = properties[i]; 
       sw.Write(prop.GetValue(item) + ","); 
      } 
      var lastProp = properties[properties.Length - 1]; 
      sw.Write(lastProp.GetValue(item) + sw.NewLine); 
     } 
    } 

Ed ecco il metodo che li unisce e crea il file vero e proprio.

public static void CreateCSV<T>(List<T> list, string filePath) 
    { 
     using (StreamWriter sw = new StreamWriter(filePath)) 
     { 
      CreateHeader(list, sw); 
      CreateRows(list, sw); 
     } 
    } 
+1

Funziona molto bene. Ho migliorato questo per passare il delimitatore come parametro, in modo da poter generare qualsiasi tipo di file delimitato. I CSV sono difficili da gestire se il testo contiene virgole, quindi creo file '| delimitati usando la versione migliorata. Grazie! – Shiva

1

biblioteca CsvHelper è molto popolare nel Nuget.You vale la pena, l'uomo! https://github.com/JoshClose/CsvHelper/wiki/Basics

Utilizzare CsvHelper è veramente facile. Le sue impostazioni predefinite sono configurate per gli scenari più comuni.

Ecco alcuni dati di installazione.

Actors.csv:

Id,FirstName,LastName 
1,Arnold,Schwarzenegger 
2,Matt,Damon 
3,Christian,Bale 

Attore.cs (oggetto personalizzato classe che rappresenta un attore):

public class Actor 
{ 
    public int Id { get; set; } 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
} 

Leggere il file CSV utilizzando CSVReader:

var csv = new CsvReader(new StreamReader("Actors.csv")); 

var = actorsList csv.GetRecords();

Scrittura in un file CSV.

using (var csv = new CsvWriter(new StreamWriter("Actors.csv"))) 
{ 
    csv.WriteRecords(actorsList); 
} 
0

Scopo generale ToCsv() metodo di estensione:

  • appoggia Int16/32/64, float, double, decimale, e qualsiasi supporto ToString()
  • opzionale personalizzato join separatore
  • Selettore personalizzato opzionale
  • Specifiche di gestione null/vuoto facoltative (sovraccarichi di * Opt())

Esempi di utilizzo:

"123".ToCsv() // "1,2,3" 
"123".ToCsv(", ") // "1, 2, 3" 
new List<int> { 1, 2, 3 }.ToCsv() // "1,2,3" 

new List<Tuple<int, string>> 
{ 
    Tuple.Create(1, "One"), 
    Tuple.Create(2, "Two") 
} 
.ToCsv(t => t.Item2); // "One,Two" 

((string)null).ToCsv() // throws exception 
((string)null).ToCsvOpt() // "" 
((string)null).ToCsvOpt(ReturnNullCsv.WhenNull) // null 

attuazione

/// <summary> 
/// Specifies when ToCsv() should return null. Refer to ToCsv() for IEnumerable[T] 
/// </summary> 
public enum ReturnNullCsv 
{ 
    /// <summary> 
    /// Return String.Empty when the input list is null or empty. 
    /// </summary> 
    Never, 

    /// <summary> 
    /// Return null only if input list is null. Return String.Empty if list is empty. 
    /// </summary> 
    WhenNull, 

    /// <summary> 
    /// Return null when the input list is null or empty 
    /// </summary> 
    WhenNullOrEmpty, 

    /// <summary> 
    /// Throw if the argument is null 
    /// </summary> 
    ThrowIfNull 
} 

/// <summary> 
/// Converts IEnumerable list of values to a comma separated string values. 
/// </summary> 
/// <typeparam name="T"></typeparam> 
/// <param name="values">The values.</param>   
/// <param name="joinSeparator"></param> 
/// <returns>System.String.</returns> 
public static string ToCsv<T>(
    this IEnumerable<T> values,    
    string joinSeparator = ",") 
{ 
    return ToCsvOpt<T>(values, null /*selector*/, ReturnNullCsv.ThrowIfNull, joinSeparator); 
} 

/// <summary> 
/// Converts IEnumerable list of values to a comma separated string values. 
/// </summary> 
/// <typeparam name="T"></typeparam> 
/// <param name="values">The values.</param> 
/// <param name="selector">An optional selector</param> 
/// <param name="joinSeparator"></param> 
/// <returns>System.String.</returns> 
public static string ToCsv<T>(
    this IEnumerable<T> values, 
    Func<T, string> selector,    
    string joinSeparator = ",") 
{ 
    return ToCsvOpt<T>(values, selector, ReturnNullCsv.ThrowIfNull, joinSeparator); 
} 

/// <summary> 
/// Converts IEnumerable list of values to a comma separated string values. 
/// </summary> 
/// <typeparam name="T"></typeparam> 
/// <param name="values">The values.</param> 
/// <param name="returnNullCsv">Return mode (refer to enum ReturnNullCsv).</param> 
/// <param name="joinSeparator"></param> 
/// <returns>System.String.</returns> 
public static string ToCsvOpt<T>(
    this IEnumerable<T> values, 
    ReturnNullCsv returnNullCsv = ReturnNullCsv.Never, 
    string joinSeparator = ",") 
{ 
    return ToCsvOpt<T>(values, null /*selector*/, returnNullCsv, joinSeparator); 
} 

/// <summary> 
/// Converts IEnumerable list of values to a comma separated string values. 
/// </summary> 
/// <typeparam name="T"></typeparam> 
/// <param name="values">The values.</param> 
/// <param name="selector">An optional selector</param> 
/// <param name="returnNullCsv">Return mode (refer to enum ReturnNullCsv).</param> 
/// <param name="joinSeparator"></param> 
/// <returns>System.String.</returns> 
public static string ToCsvOpt<T>(
    this IEnumerable<T> values, 
    Func<T, string> selector, 
    ReturnNullCsv returnNullCsv = ReturnNullCsv.Never, 
    string joinSeparator = ",") 
{ 
    switch (returnNullCsv) 
    { 
     case ReturnNullCsv.Never: 
      if (!values.AnyOpt()) 
       return string.Empty; 
      break; 

     case ReturnNullCsv.WhenNull: 
      if (values == null) 
       return null; 
      break; 

     case ReturnNullCsv.WhenNullOrEmpty: 
      if (!values.AnyOpt()) 
       return null; 
      break; 

     case ReturnNullCsv.ThrowIfNull: 
      if (values == null) 
       throw new ArgumentOutOfRangeException("ToCsvOpt was passed a null value with ReturnNullCsv = ThrowIfNull."); 
      break; 

     default: 
      throw new ArgumentOutOfRangeException("returnNullCsv", returnNullCsv, "Out of range."); 
    } 

    if (selector == null) 
    { 
     if (typeof(T) == typeof(Int16) || 
      typeof(T) == typeof(Int32) || 
      typeof(T) == typeof(Int64)) 
     {     
      selector = (v) => Convert.ToInt64(v).ToStringInvariant(); 
     } 
     else if (typeof(T) == typeof(decimal)) 
     { 
      selector = (v) => Convert.ToDecimal(v).ToStringInvariant(); 
     } 
     else if (typeof(T) == typeof(float) || 
       typeof(T) == typeof(double)) 
     { 
      selector = (v) => Convert.ToDouble(v).ToString(CultureInfo.InvariantCulture); 
     } 
     else 
     { 
      selector = (v) => v.ToString(); 
     }    
    } 

    return String.Join(joinSeparator, values.Select(v => selector(v))); 
} 

public static string ToStringInvariantOpt(this Decimal? d) 
{ 
    return d.HasValue ? d.Value.ToStringInvariant() : null; 
} 

public static string ToStringInvariant(this Decimal d) 
{ 
    return d.ToString(CultureInfo.InvariantCulture); 
} 

public static string ToStringInvariantOpt(this Int64? l) 
{ 
    return l.HasValue ? l.Value.ToStringInvariant() : null; 
} 

public static string ToStringInvariant(this Int64 l) 
{ 
    return l.ToString(CultureInfo.InvariantCulture); 
} 

public static string ToStringInvariantOpt(this Int32? i) 
{ 
    return i.HasValue ? i.Value.ToStringInvariant() : null; 
} 

public static string ToStringInvariant(this Int32 i) 
{ 
    return i.ToString(CultureInfo.InvariantCulture); 
} 

public static string ToStringInvariantOpt(this Int16? i) 
{ 
    return i.HasValue ? i.Value.ToStringInvariant() : null; 
} 

public static string ToStringInvariant(this Int16 i) 
{ 
    return i.ToString(CultureInfo.InvariantCulture); 
} 
Problemi correlati