2009-12-11 12 views
108

J'ai une liste de valeurs entières (List) et j'aimerais générer une chaîne de valeurs séparées par des virgules. Tous les éléments de la liste sont affichés dans une seule liste délimitée par des virgules.Conversion d'une liste générique en chaîne CSV

Mes pensées ... 1. Passez la liste à une méthode. 2. Utilisez stringbuilder pour parcourir la liste et ajouter des virgules. 3. Testez le dernier caractère et, s'il s'agit d'une virgule, supprimez-le.

Que pensez-vous? Est-ce la meilleure façon?

Comment mon code changerait-il si je voulais gérer non seulement des entiers (mon plan actuel) mais des chaînes, des longs, des doubles, des booléens, etc., etc. dans le futur? Je suppose qu'il accepte une liste de n'importe quel type.

Répondre

189

C'est incroyable ce que le Framework fait déjà pour nous.

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

Pour le cas général:

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

Comme vous pouvez le voir, il est effectivement pas différent. Méfiez-vous que vous devrez peut-être envelopper x.ToString() entre guillemets (c'est-à-dire, "\"" + x.ToString() + "\"") dans le cas x.ToString() contient des virgules.

Pour une lecture intéressante sur une légère variante de ceci: voir Comma Quibbling sur le blog d'Eric Lippert.

Remarque: Ceci a été écrit avant la publication officielle de .NET 4.0. Maintenant, nous pouvons simplement dire

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

en utilisant la surcharge String.Join<T>(string, IEnumerable<T>). Cette méthode projette automatiquement chaque élément x à x.ToString().

+0

'' Liste ne possède pas la méthode 'select' dans le cadre 3.5, sauf si je manque quelque chose. – ajeh

+2

@ajeh: Il vous manque probablement une instruction 'using'. – jason

+0

Quelle importation spécifique? – ajeh

7

Vous pouvez utiliser String.Join.

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

Pas besoin de spécifier les paramètres de type générique dans l'appel de 'ConvertAll' ici -' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' ' –

+0

Merci pour le conseil. –

+1

Au lieu de faire 'Array.ConvertAll (... 'vous pouvez simplement faire' list.ConvertAll (e => e.ToString()). ToArray) ', en tapant moins. – David

10

Vous pouvez créer une méthode d'extension que vous pouvez faire appel à toute 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()); 
} 

Ensuite, vous pouvez simplement appeler la méthode sur la liste initiale:

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

à 3.5, j'étais encore capable de faire ça. C'est beaucoup plus simple et n'a pas besoin de lambda.

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

'ToArray()' méthode de 'Liste ' ne peut pas être utilisé avec l'argument de type dans la structure 3.5 sauf si quelque chose me manque. – ajeh

3

Tout travail de solution que si la liste d'une liste (de chaîne)

Si vous avez une liste générique de vos objets comme la liste (de voiture) où la voiture a des propriétés n, vous devez boucler la PropertiesInfo de chaque objet de voiture.

Regardez: http://www.csharptocsharp.com/generate-csv-from-generic-list

+1

ne pouvez pas remplacer ToString de la classe et utiliser les méthodes ci-dessus? –

5

Si un organisme veut convertir la liste des objets classe personnalisée au lieu de liste de chaînes puis remplacer la méthode ToString de votre classe avec une représentation de la ligne de csv de votre classe.

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

Ensuite, le code suivant peut être utilisé pour convertir cette liste de classe au format CSV avec colonne d'en-tête

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

Comme le code dans le lien donné par @Frank Create a CSV File from a .NET Generic List il y avait un petit problème de terminer chaque ligne avec un , J'ai modifié le code pour se débarrasser de celui-ci.Espérons qu'il aide quelqu'un.

/// <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

Informations supplémentaires: Le processus ne peut pas accéder au fichier 'c: \ temp \ matchingMainWav.csv' car il est utilisé par un autre processus. le dossier 'dev' existe, mais pas le fichier ... je n'utilise pas ce droit? –

+0

La méthode File.Create crée le fichier et ouvre un FileStream sur le fichier. Donc, votre fichier est déjà ouvert. Vous n'avez pas vraiment besoin de la méthode file.Create: – David

3

J'aime une méthode d'extension simple et agréable

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

Ensuite, vous pouvez simplement appeler la méthode sur la liste initiale:

string CsvString = myList.ToCsv(); 

propre et plus facile à lire que certains des autres suggestions.

0

http://cc.davelozinski.com/c-sharp/the-fastest-way-to-read-and-process-text-files

Ce site fait quelques tests approfondis sur la façon d'écrire dans un fichier en utilisant l'écrivain en mémoire tampon, la lecture ligne par ligne semble être la meilleure façon, en utilisant le constructeur de chaîne a été l'un des plus lents. J'utilise ses techniques beaucoup pour écrire des choses à classer ça fonctionne bien.

0

Le problème avec String.Join est que vous ne gérez pas le cas d'une virgule déjà existante dans la valeur. Lorsqu'une virgule existe, vous entourez la valeur dans Cotations et remplacez toutes les Cotations existantes par des Cotations doubles.

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

Voir CSV Module

2

Je l'explique en profondeur dans ce post. Je vais juste coller le code ici avec de brèves descriptions.

Voici la méthode qui crée la ligne d'en-tête. Il utilise les noms de propriété comme noms de colonnes.

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

Cette méthode crée toutes les lignes de valeur

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

Et voici la méthode qui les rassemble et crée le fichier réel.

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

Cela fonctionne très bien. J'ai amélioré cela pour passer le délimiteur en tant que paramètre, donc n'importe quel type de fichier délimité peut être généré. Les CSV sont difficiles à traiter si le texte contient des virgules, donc je génère des fichiers délimités '|' en utilisant la version améliorée. Merci! – Shiva

1

La bibliothèque CsvHelper est très populaire dans le Nuget.Vous le valez bien, mec! https://github.com/JoshClose/CsvHelper/wiki/Basics

Utiliser CsvHelper est vraiment facile. Ses paramètres par défaut sont configurés pour les scénarios les plus courants.

Voici quelques données d'installation.

Actors.csv:

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

Acteur.cs (objet de classe personnalisée qui représente un acteur):

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

Lecture du fichier CSV à l'aide CSVReader:

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

var = actorsList csv.GetRecords();

Écriture dans un fichier CSV.

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

Un procédé d'extension d'usage général ToCSV():

  • soutient Int16/32/64, float, double, décimal, et tout support ToString()
  • personnalisé facultatif jointure séparateur
  • Sélecteur personnalisé en option
  • Spécification de traitement null/vide facultatif (surcharges * Opt())

Exemples d'utilisation:

"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 

mise en œuvre

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