Mengonversi daftar umum menjadi string CSV


142

Saya memiliki daftar nilai integer (List) dan ingin menghasilkan string nilai yang dipisahkan koma. Itu semua item dalam daftar keluaran ke daftar dipisahkan koma tunggal.

Pikiranku ... 1. meneruskan daftar ke metode. 2. Gunakan stringbuilder untuk mengulang daftar dan menambahkan koma 3. Uji karakter terakhir dan jika itu adalah koma, hapus.

Apa yang kamu pikirkan Apakah ini cara terbaik?

Bagaimana kode saya berubah jika saya ingin menangani tidak hanya integer (paket saya saat ini) tetapi juga string, long, doubles, bools, dll, dll. Di masa mendatang? Saya kira membuatnya menerima daftar jenis apa pun.

Jawaban:


250

Sungguh menakjubkan apa yang telah dilakukan Kerangka untuk kami.

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

Untuk kasus umum:

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

Seperti yang Anda lihat, secara efektif tidak berbeda. Berhati-hatilah karena Anda mungkin perlu membungkus x.ToString()dalam tanda kutip (yaitu, "\"" + x.ToString() + "\"") jika x.ToString()berisi koma.

Untuk bacaan yang menarik tentang sedikit variasi dari ini: lihat Comma Quibbling di blog Eric Lippert.

Catatan: Ini ditulis sebelum .NET 4.0 secara resmi dirilis. Sekarang kita bisa katakan

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

menggunakan kelebihan beban String.Join<T>(string, IEnumerable<T>). Metode ini secara otomatis akan memproyeksikan setiap elemen xke x.ToString().


List<int>tidak memiliki metode Selectdalam kerangka 3.5 kecuali saya kehilangan sesuatu.
ajeh

2
@ajeh: Anda mungkin melewatkan usingpernyataan.
jason

Impor spesifik yang mana?
ajeh

1
Coba System.Linq.Enumerable(dan tentu saja Anda perlu System.Core.dllperakitan, tetapi mungkin Anda sudah memilikinya). Anda lihat, List<int> tidak pernah memiliki Selectmetode. Sebaliknya, System.Linq.Enumerablemendefinisikan Selectsebagai metode ekstensi IEnumerable<T>, yang List<int>merupakan contoh dari. Jadi, Anda perlu System.Linq.Enumerablemengimpor untuk memilih metode ekstensi ini.
jason

1
tidak ada jawaban Anda yang berhasil untuk saya. Saya hanya mendapatkan satu baris string yang dipisahkan koma yang hanya berisi nama jenis dari daftar generik <T>
Thameem

15

di 3,5, saya masih bisa melakukan ini. Ini jauh lebih sederhana dan tidak membutuhkan lambda.

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

ToArray()Metode List<int>tidak dapat digunakan dengan argumen tipe dalam kerangka 3.5 kecuali saya kehilangan sesuatu.
ajeh

1
Cemerlang. Tidak perlu ToArray <string> karena anak ToString () digunakan.
Christian

11

Anda dapat membuat metode ekstensi yang dapat Anda panggil di IEnumerable mana pun:

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

Kemudian Anda bisa memanggil metode di daftar asli:

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

7

Anda bisa menggunakan String.Join.

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

Tidak perlu menentukan parameter tipe generik dalam panggilan ke ConvertAllsini - keduanya intdan stringakan disimpulkan.
Pavel Minaev

1
Daripada melakukan Array.ConvertAll(...' you can just do list.ConvertAll (e => e.ToString ()). ToArray) `, cukup sedikit mengetik.
David

string.Join (",", list); akan baik-baik saja :)
Christian

6

Jika ada badan yang ingin mengonversi daftar objek kelas khusus, bukan daftar string, maka ganti metode ToString kelas Anda dengan representasi baris csv kelas Anda.

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

Kemudian kode berikut dapat digunakan untuk mengubah daftar kelas ini menjadi CSV dengan kolom header

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 sebagai gantinya MyClass.Select
Piotr Ferenc

6

Saya menjelaskannya secara mendalam di posting ini . Saya hanya akan menempelkan kode di sini dengan deskripsi singkat.

Inilah metode yang membuat baris tajuk. Ini menggunakan nama properti sebagai nama kolom.

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

Metode ini membuat semua baris nilai

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

Dan inilah metode yang menyatukan mereka dan membuat file yang sebenarnya.

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

1
Ini bekerja dengan sangat baik. Saya meningkatkan ini untuk meneruskan pembatas sebagai parameter, sehingga semua jenis file yang dipisahkan dapat dibuat. CSV sulit untuk ditangani jika teks berisi koma, jadi saya membuat |file yang dipisahkan menggunakan versi yang ditingkatkan. Terima kasih!
Siwa

5

Karena kode dalam tautan yang diberikan oleh @Frank Buat File CSV dari Daftar Generik .NET, ada sedikit masalah untuk mengakhiri setiap baris dengan ,saya memodifikasi kode untuk menghilangkannya. Semoga ini membantu seseorang.

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

        }
    }
}

Informasi tambahan: Proses tidak dapat mengakses file 'c: \ temp \ matchingMainWav.csv' karena sedang digunakan oleh proses lain. foldernya devada, tetapi bukan filenya ... apakah saya tidak menggunakannya, bukan?
Tom Stickel

Metode File.Create membuat file dan membuka FileStream pada file tersebut. Jadi file Anda sudah terbuka. Anda tidak benar-benar membutuhkan file tersebut. Buat metode sama sekali:
David

Jika ada properti yang nol, apakah ada cara lain?
Daniel Jackson

@DanielJackson Anda dapat menulis klausa di mana dalam pernyataan ini sw.Write(string.Join(",", props.Select(d => d.Name).ToArray()) + newLine);Tidak diuji tetapi tidak tahu apa yang ingin Anda capai
Ali Umair

4

Saya suka metode ekstensi sederhana yang bagus

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

Kemudian Anda bisa memanggil metode di daftar asli:

string CsvString = myList.ToCsv();

Lebih bersih dan lebih mudah dibaca daripada beberapa saran lainnya.



3

Library CsvHelper Sangat Populer Di Nuget. Layak Banget! https://github.com/JoshClose/CsvHelper/wiki/Basics

Menggunakan CsvHelper sangat mudah. Pengaturan defaultnya disiapkan untuk skenario yang paling umum.

Berikut ini sedikit data penyiapan.

Actors.csv:

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

Actor.cs (objek kelas khusus yang mewakili aktor):

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

Membaca file CSV menggunakan CsvReader:

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

var actorList = csv.GetRecords ();

Menulis ke file CSV.

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

2

Masalah dengan String.Join adalah Anda tidak menangani kasus koma yang sudah ada di nilainya. Jika ada koma, Anda mengapit nilai di Quotes dan mengganti semua Quotes yang ada dengan Quotes ganda.

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

Lihat Modul CSV


2

Untuk alasan apa pun, @AliUmair mengembalikan hasil edit ke jawabannya yang memperbaiki kodenya yang tidak berjalan sebagaimana mestinya, jadi berikut adalah versi yang berfungsi yang tidak memiliki kesalahan akses file dan menangani nilai properti objek null dengan benar:

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

Metode ekstensi ToCsv () tujuan umum:

  • Mendukung Int16 / 32/64, float, double, desimal, dan apa pun yang mendukung ToString ()
  • Pemisah gabungan kustom opsional
  • Pemilih kustom opsional
  • Spesifikasi penanganan kosong / kosong opsional (* Opt () kelebihan beban)

Contoh Penggunaan:

"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

Penerapan

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

1

Ini adalah metode ekstensi saya, ia mengembalikan string untuk kesederhanaan tetapi implementasi saya menulis file ke danau data.

Ini menyediakan pembatas apa pun, menambahkan tanda kutip ke string (jika mengandung pemisah) dan penawaran akan kosong dan kosong.

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

Pemakaian:

 // Tab Delimited (TSV)
 var csv = MyList.ToCsv<MyClass>("\t");
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.