Conversion d'une liste générique en chaîne CSV

139

J'ai une liste de valeurs entières (List) et je voudrais générer une chaîne de valeurs délimitées par des virgules. Ce sont tous les éléments de la liste qui sont sortis dans une seule liste délimitée par des virgules.

Mes pensées ... 1. transmettez 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.

Quelles sont vos pensées? Est-ce le meilleur moyen?

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

DenaliHardtail
la source

Réponses:

243

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, ce n'est effectivement pas différent. Attention, vous devrez peut-être mettre x.ToString()entre guillemets (c'est-à-dire "\"" + x.ToString() + "\"") au cas où il y aurait x.ToString()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 on peut juste dire

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

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

Jason
la source
List<int>n'a pas de méthode Selectdans le framework 3.5 sauf si je manque quelque chose.
ajeh
2
@ajeh: Il vous manque probablement une usingdéclaration.
jason
Quelle importation spécifique?
ajeh
1
Essayez System.Linq.Enumerable(et bien sûr vous aurez besoin d' System.Core.dllassemblage, mais vous l'avez probablement déjà). Vous voyez, List<int> jamais a Selectcomme méthode. Au contraire, System.Linq.Enumerabledéfinit Selectcomme une méthode d'extension sur IEnumerable<T>, qui List<int>est un exemple de. Ainsi, vous devez System.Linq.Enumerabledans vos importations choisir cette méthode d'extension.
jason
Si vous avez affaire à des valeurs numériques et que les virgules posent un problème (selon les paramètres régionaux), une alternative est x.ToString(CultureInfo.InvariantCulture). Cela utilisera le point comme séparateur décimal.
heltonbiker
15

en 3.5, j'étais toujours capable de le faire. C'est beaucoup plus simple et n'a pas besoin de lambda.

String.Join(",", myList.ToArray<string>());
Krishna
la source
ToArray()La méthode de List<int>ne peut pas être utilisée avec l'argument de type dans le framework 3.5 sauf si quelque chose me manque.
ajeh
Brillant. Il n'y a pas besoin de ToArray <string> car l'enfant ToString () est utilisé.
Christian
11

Vous pouvez créer une méthode d'extension que vous pouvez appeler sur n'importe quel 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 d'origine:

string commaSeparated = myList.JoinStrings(", ");
Qu'est ce que c'est
la source
7

Vous pouvez utiliser String.Join.

String.Join(
  ",",
  Array.ConvertAll(
     list.ToArray(),
     element => element.ToString()
  )
);
João Angelo
la source
Pas besoin de spécifier des paramètres de type générique dans l'appel à ConvertAllici - les deux intet stringseront déduits.
Pavel Minaev
1
Au lieu de faire Array.ConvertAll(...' you can just do list.ConvertAll (e => e.ToString ()). ToArray) `, juste moins de saisie.
David
string.Join (",", liste); fera très bien :)
Christian
6

Si un corps souhaite convertir une liste d' objets de classe personnalisés au lieu d'une liste de chaînes, remplacez la méthode ToString de votre classe par une représentation de ligne 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 classes en CSV avec la 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());
Zain Ali
la source
myExampleCollection.Select à la place MyClass.Select
Piotr Ferenc
5

Comme le code dans le lien donné par @Frank Créer un fichier CSV à partir d'une liste générique .NET, il y avait un petit problème de fin de chaque ligne par un ,J'ai modifié le code pour s'en débarrasser. J'espère que cela 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);

        }
    }
}
Ali Umair
la source
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 devexiste, mais pas le fichier ... ne suis-je pas en train de l'utiliser?
Tom Stickel
La méthode File.Create crée le fichier et ouvre un FileStream sur le fichier. Votre dossier est donc déjà ouvert. Vous n'avez pas vraiment besoin du fichier.Méthode de création du tout:
David
Si des propriétés sont nulles, existe-t-il un moyen de contourner cela?
Daniel Jackson du
@DanielJackson Vous pouvez écrire une clause where dans cette déclaration sw.Write(string.Join(",", props.Select(d => d.Name).ToArray()) + newLine);Non testé mais je ne sais pas ce que vous essayez d'accomplir
Ali Umair
4

Je l'explique en profondeur dans ce post . Je vais simplement 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 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);
    }

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);
        }
    }
SQLSuperHero
la source
1
Cela fonctionne très bien. J'ai amélioré cela pour passer le délimiteur en tant que paramètre, afin que tout type de fichier délimité puisse être généré. Les CSV sont difficiles à gérer si le texte contient des virgules, je génère donc des |fichiers délimités en utilisant la version améliorée. Merci!
Shiva
3

Toute solution ne fonctionne que si List a list (of string)

Si vous avez une liste générique de vos propres objets comme list (of car) où car a n propriétés, vous devez boucler le PropertiesInfo de chaque objet car.

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

Frank M.
la source
1
ne pouvez-vous pas remplacer ToString de la classe et utiliser les méthodes ci-dessus?
Gabriel Guimarães
3

J'aime une belle méthode d'extension simple

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

Ensuite, vous pouvez simplement appeler la méthode sur la liste d'origine:

string CsvString = myList.ToCsv();

Plus propre et plus facile à lire que certaines des autres suggestions.

Griffon
la source
2

Le problème avec String.Join est que vous ne gérez pas la casse d'une virgule déjà existante dans la valeur. Lorsqu'une virgule existe, vous entourez la valeur des guillemets et remplacez tous les devis existants par des guillemets doubles.

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

Voir le module CSV

vbjay
la source
2

La bibliothèque CsvHelper est très populaire dans le Nuget, ça vaut le coup, mec! https://github.com/JoshClose/CsvHelper/wiki/Basics

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

Voici quelques données de configuration.

Actors.csv:

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

Actor.cs (objet de classe personnalisé 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 de CsvReader:

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

var acteursList = csv.GetRecords ();

Écriture dans un fichier CSV.

using (var csv = new CsvWriter( new StreamWriter( "Actors.csv" ) )) 
{
    csv.WriteRecords( actorsList );
}
Farb
la source
2

Pour une raison quelconque, @AliUmair a rétabli la modification de sa réponse qui corrige son code qui ne s'exécute pas tel quel, voici donc la version de travail qui n'a pas l'erreur d'accès au fichier et gère correctement les valeurs de propriété d'objet nulles:

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

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

        }
    }
}
Lunyx
la source
1

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

Ce site Web a fait des tests approfondis sur la façon d'écrire dans un fichier en utilisant un écrivain en mémoire tampon, la lecture ligne par ligne semble être le meilleur moyen, l'utilisation du générateur de chaînes était l'une des plus lentes.

J'utilise beaucoup ses techniques pour écrire des trucs pour classer ça marche bien.

Chong Ching
la source
1

Une méthode d'extension ToCsv () à usage général:

  • Prend en charge Int16 / 32/64, float, double, décimal et tout ce qui prend en charge ToString ()
  • Séparateur de jointure personnalisé en option
  • Sélecteur personnalisé en option
  • Spécification de gestion facultative null / vide (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

la mise en oeuvre

/// <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);
}
crokusek
la source
0

Voici ma méthode d'extension, elle renvoie une chaîne pour plus de simplicité mais mon implémentation écrit le fichier dans un lac de données.

Il fournit n'importe quel délimiteur, ajoute des guillemets à la chaîne (au cas où ils contiennent le délimiteur) et traite des nuls et des blancs.

    /// <summary>
    /// A class to hold extension methods for C# Lists 
    /// </summary>
    public static class ListExtensions
    {
        /// <summary>
        /// Convert a list of Type T to a CSV
        /// </summary>
        /// <typeparam name="T">The type of the object held in the list</typeparam>
        /// <param name="items">The list of items to process</param>
        /// <param name="delimiter">Specify the delimiter, default is ,</param>
        /// <returns></returns>
        public static string ToCsv<T>(this List<T> items, string delimiter = ",")
        {
            Type itemType = typeof(T);
            var props = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name);

            var csv = new StringBuilder();

            // Write Headers
            csv.AppendLine(string.Join(delimiter, props.Select(p => p.Name)));

            // Write Rows
            foreach (var item in items)
            {
                // Write Fields
                csv.AppendLine(string.Join(delimiter, props.Select(p => GetCsvFieldasedOnValue(p, item))));
            }

            return csv.ToString();
        }

        /// <summary>
        /// Provide generic and specific handling of fields
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        private static object GetCsvFieldasedOnValue<T>(PropertyInfo p, T item)
        {
            string value = "";

            try
            {
                value = p.GetValue(item, null)?.ToString();
                if (value == null) return "NULL";  // Deal with nulls
                if (value.Trim().Length == 0) return ""; // Deal with spaces and blanks

                // Guard strings with "s, they may contain the delimiter!
                if (p.PropertyType == typeof(string))
                {
                    value = string.Format("\"{0}\"", value);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return value;
        }
    }

Usage:

 // Tab Delimited (TSV)
 var csv = MyList.ToCsv<MyClass>("\t");
Murray Foxcroft
la source