Konvertieren einer generischen Liste in eine CSV-Zeichenfolge


139

Ich habe eine Liste mit ganzzahligen Werten (Liste) und möchte eine Zeichenfolge mit durch Kommas getrennten Werten generieren. Das sind alle Elemente in der Liste, die in eine durch Kommas getrennte Liste ausgegeben werden.

Meine Gedanken ... 1. Übergeben Sie die Liste an eine Methode. 2. Verwenden Sie den Stringbuilder, um die Liste zu durchlaufen und Kommas anzuhängen. 3. Testen Sie das letzte Zeichen. Wenn es sich um ein Komma handelt, löschen Sie es.

Was sind deine Gedanken? Ist das der beste Weg?

Wie würde sich mein Code ändern, wenn ich in Zukunft nicht nur Ganzzahlen (meinen aktuellen Plan), sondern auch Zeichenfolgen, Longs, Doubles, Bools usw. usw. verarbeiten möchte? Ich denke, es wird eine Liste jeglicher Art akzeptieren.

Antworten:


243

Es ist erstaunlich, was das Framework bereits für uns tut.

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

Für den allgemeinen Fall:

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

Wie Sie sehen können, ist es praktisch nicht anders. Beachten Sie, dass Sie möglicherweise tatsächlich x.ToString()Anführungszeichen (dh "\"" + x.ToString() + "\"") in Anführungszeichen setzen müssen, falls x.ToString()Kommas enthalten sind.

Eine interessante Lektüre einer kleinen Variante finden Sie unter Comma Quibbling in Eric Lipperts Blog.

Hinweis: Dies wurde geschrieben, bevor .NET 4.0 offiziell veröffentlicht wurde. Jetzt können wir nur sagen

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

mit der Überlastung String.Join<T>(string, IEnumerable<T>). Diese Methode projiziert automatisch jedes Element xauf x.ToString().


List<int>hat keine Methode Selectin Framework 3.5, es sei denn, ich vermisse etwas.
Ajeh

2
@ajeh: Du vermisst wahrscheinlich eine usingAussage.
Jason

Welcher spezifische Import?
Ajeh

1
Versuchen Sie es System.Linq.Enumerable(und natürlich müssen Sie zusammenbauen System.Core.dll, aber vermutlich haben Sie das bereits). Sie sehen, List<int> nie hat Selectals Methode. Vielmehr System.Linq.Enumerabledefiniert Selectals eine Erweiterung Methode auf IEnumerable<T>, von denen List<int>ein Beispiel. Daher müssen Sie System.Linq.Enumerablebei Ihren Importen diese Erweiterungsmethode übernehmen.
Jason

Wenn Sie mit numerischen Werten arbeiten und Kommas ein Problem darstellen (abhängig vom Gebietsschema), ist eine Alternative x.ToString(CultureInfo.InvariantCulture). Dies verwendet Punkt als Dezimaltrennzeichen.
Heltonbiker

15

in 3.5 konnte ich das noch. Es ist viel einfacher und braucht kein Lambda.

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

ToArray()Die Methode von List<int>kann nicht mit dem Typargument in Framework 3.5 verwendet werden, es sei denn, mir fehlt etwas.
Ajeh

Brillant. ToArray <string> ist nicht erforderlich, da untergeordnetes ToString () verwendet wird.
Christian

11

Sie können eine Erweiterungsmethode erstellen, die Sie für jede IEnumerable aufrufen können:

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

Dann können Sie einfach die Methode in der ursprünglichen Liste aufrufen:

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

7

Sie können verwenden String.Join.

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

Hier müssen keine generischen Typparameter angegeben ConvertAllwerden - beides intund stringwird abgeleitet.
Pavel Minaev

1
Anstatt Array.ConvertAll(...' you can just do list.ConvertAll (e => e.ToString ()). ToArray) `auszuführen, nur weniger tippen.
David

string.Join (",", Liste); wird gut tun :)
Christian

6

Wenn ein Text die Liste der benutzerdefinierten Klassenobjekte anstelle der Liste der Zeichenfolgen konvertieren möchte, überschreiben Sie die ToString-Methode Ihrer Klasse mit der CSV-Zeilendarstellung Ihrer Klasse.

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

Anschließend kann der folgende Code verwendet werden, um diese Klassenliste in CSV mit Kopfzeile zu konvertieren

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

myExampleCollection.Select statt MyClass.Select
Piotr Ferenc

5

Da der Code in dem von @Frank angegebenen Link eine CSV-Datei aus einer generischen .NET-Liste erstellen, gab es ein kleines Problem beim Beenden jeder Zeile mit einem ,Ich habe den Code geändert, um ihn zu entfernen. Hoffentlich hilft er jemandem.

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

        }
    }
}

Zusätzliche Informationen: Der Prozess kann nicht auf die Datei 'c: \ temp \ matchingMainWav.csv' zugreifen, da sie von einem anderen Prozess verwendet wird. Der Ordner devexistiert, aber nicht die Datei ... verwende ich das nicht richtig?
Tom Stickel

Die File.Create-Methode erstellt die Datei und öffnet einen FileStream für die Datei. Ihre Datei ist also bereits geöffnet. Sie brauchen die Datei nicht wirklich. Erstellen Sie Methode überhaupt:
David

Wenn Eigenschaften null sind, gibt es einen Weg, dies zu umgehen?
Daniel Jackson

@ DanielJackson Sie können eine where-Klausel in diese Anweisung schreiben. sw.Write(string.Join(",", props.Select(d => d.Name).ToArray()) + newLine);Nicht getestet, aber nicht wissen, was Sie erreichen wollen
Ali Umair

4

Ich erkläre es ausführlich in diesem Beitrag . Ich füge hier nur den Code mit kurzen Beschreibungen ein.

Hier ist die Methode, mit der die Kopfzeile erstellt wird. Es verwendet die Eigenschaftsnamen als Spaltennamen.

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

Diese Methode erstellt alle Wertzeilen

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

Und hier ist die Methode, die sie zusammenbringt und die eigentliche Datei erstellt.

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

1
Das funktioniert sehr gut. Ich habe dies verbessert, um das Trennzeichen als Parameter zu übergeben, sodass jeder Typ einer begrenzten Datei generiert werden kann. CSVs sind schwierig zu handhaben, wenn der Text Kommas enthält. Daher generiere ich |begrenzte Dateien mit der verbesserten Version. Vielen Dank!
Shiva

3

Jede Lösung funktioniert nur, wenn eine Liste (von Zeichenfolgen) aufgelistet wird.

Wenn Sie eine generische Liste Ihrer eigenen Objekte wie die Liste (des Autos) haben, in der das Auto n Eigenschaften hat, müssen Sie die PropertiesInfo jedes Autoobjekts schleifen.

Schauen Sie sich an: http://www.csharptocsharp.com/generate-csv-from-generic-list


1
Können Sie ToString der Klasse nicht überschreiben und die oben genannten Methoden verwenden?
Gabriel Guimarães

3

Ich mag eine schöne einfache Erweiterungsmethode

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

Dann können Sie einfach die Methode in der ursprünglichen Liste aufrufen:

string CsvString = myList.ToCsv();

Sauberer und leichter zu lesen als einige der anderen Vorschläge.


2

Das Problem mit String.Join ist, dass Sie den Fall eines Kommas, das bereits im Wert vorhanden ist, nicht behandeln. Wenn ein Komma vorhanden ist, umgeben Sie den Wert in Anführungszeichen und ersetzen alle vorhandenen Anführungszeichen durch doppelte Anführungszeichen.

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

Siehe CSV-Modul


2

Die CsvHelper-Bibliothek ist im Nuget sehr beliebt. Sie sind es wert, Mann! https://github.com/JoshClose/CsvHelper/wiki/Basics

Die Verwendung von CsvHelper ist wirklich einfach. Die Standardeinstellungen sind für die gängigsten Szenarien eingerichtet.

Hier sind ein paar Setup-Daten.

Actors.csv:

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

Actor.cs (benutzerdefiniertes Klassenobjekt, das einen Akteur darstellt):

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

Lesen der CSV-Datei mit CsvReader:

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

var ActorsList = csv.GetRecords ();

Schreiben in eine CSV-Datei.

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

2

Aus irgendeinem Grund hat @AliUmair die Bearbeitung auf seine Antwort zurückgesetzt, mit der sein Code behoben wird, der nicht so ausgeführt wird, wie er ist. Hier ist also die Arbeitsversion, die keinen Dateizugriffsfehler aufweist und die Werte von Nullobjekteigenschaften ordnungsgemäß behandelt:

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

        }
    }
}


1

Eine allgemeine ToCsv () - Erweiterungsmethode:

  • Unterstützt Int16 / 32/64, float, double, decimal und alles, was ToString () unterstützt
  • Optionales benutzerdefiniertes Join-Trennzeichen
  • Optionale benutzerdefinierte Auswahl
  • Optionale Null / Leer-Handhabungsspezifikation (* Opt () Überladungen)

Anwendungsbeispiele:

"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

Implementierung

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

0

Hier ist meine Erweiterungsmethode, die der Einfachheit halber eine Zeichenfolge zurückgibt, aber meine Implementierung schreibt die Datei in einen Datensee.

Es sieht jedes Trennzeichen vor, fügt Anführungszeichen zur Zeichenfolge hinzu (falls sie das Trennzeichen enthalten) und behandelt Nullen und Leerzeichen.

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

Verwendung:

 // Tab Delimited (TSV)
 var csv = MyList.ToCsv<MyClass>("\t");
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.