Chuyển đổi danh sách chung thành chuỗi CSV


139

Tôi có một danh sách các giá trị nguyên (Danh sách) và muốn tạo một chuỗi các giá trị được phân cách bằng dấu phẩy. Đó là tất cả các mục trong đầu ra danh sách cho một danh sách được phân cách bằng dấu phẩy.

Suy nghĩ của tôi ... 1. chuyển danh sách cho một phương pháp. 2. Sử dụng Stringbuilder để lặp lại danh sách và nối thêm dấu phẩy 3. Kiểm tra ký tự cuối cùng và nếu đó là dấu phẩy, hãy xóa nó.

Quan điểm của bạn là gì? Đây có phải là cách tốt nhất?

Mã của tôi sẽ thay đổi như thế nào nếu tôi muốn xử lý không chỉ các số nguyên (kế hoạch hiện tại của tôi) mà cả các chuỗi, dài, đôi, bool, v.v. trong tương lai? Tôi đoán làm cho nó chấp nhận một danh sách của bất kỳ loại.

Câu trả lời:


243

Thật đáng kinh ngạc những gì Framework đã làm cho chúng ta.

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

Đối với trường hợp chung:

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

Như bạn có thể thấy, nó thực sự không khác. Coi chừng bạn có thể cần phải thực sự bọc x.ToString()trong dấu ngoặc kép (nghĩa là "\"" + x.ToString() + "\"") trong trường hợp x.ToString()có dấu phẩy.

Để đọc một cách thú vị về một biến thể nhỏ của điều này: hãy xem Dấu phẩy trên blog của Eric Lippert.

Lưu ý: Điều này đã được viết trước khi .NET 4.0 được phát hành chính thức. Bây giờ chúng ta chỉ có thể nói

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

sử dụng quá tải String.Join<T>(string, IEnumerable<T>). Phương pháp này sẽ tự động chiếu mỗi yếu tố xđể x.ToString().


List<int>không có phương thức Selecttrong khung 3.5 trừ khi tôi thiếu một cái gì đó.
ajeh

2
@ajeh: Có lẽ bạn đang thiếu một usingtuyên bố.
jason

Nhập khẩu cụ thể nào?
ajeh

1
Hãy thử System.Linq.Enumerable(và tất nhiên bạn sẽ cần System.Core.dlllắp ráp, nhưng có lẽ bạn đã có điều đó). Bạn thấy đấy, List<int> không bao giờSelectmột phương pháp. Thay vào đó, System.Linq.Enumerableđịnh nghĩa Selectlà một phương thức mở rộng trên IEnumerable<T>, trong đó List<int>là một ví dụ về. Vì vậy, bạn cần System.Linq.Enumerablenhập hàng để chọn phương thức mở rộng này.
jason

Nếu bạn đang xử lý các giá trị số và dấu phẩy là một vấn đề (tùy thuộc vào miền địa phương), một giải pháp thay thế là x.ToString(CultureInfo.InvariantCulture). Điều này sẽ sử dụng thời gian như dấu phân cách thập phân.
heltonbiker

15

trong 3,5, tôi vẫn có thể làm điều này. Nó đơn giản hơn nhiều và không cần lambda.

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

ToArray()phương thức List<int>không thể được sử dụng với đối số kiểu trong khung 3.5 trừ khi tôi thiếu một cái gì đó.
ajeh

Xuất sắc. Không cần ToArray <string> khi con ToString () được sử dụng.
Christian

11

Bạn có thể tạo một phương thức mở rộng mà bạn có thể gọi trên bất kỳ IEnumerable nào:

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

Sau đó, bạn chỉ có thể gọi phương thức trong danh sách ban đầu:

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

7

Bạn có thể sử dụng String.Join.

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

Không cần chỉ định tham số loại chung trong cuộc gọi đến ConvertAllđây - cả hai intstringsẽ được suy ra.
Pavel Minaev

1
Thay vì thực hiện Array.ConvertAll(...' you can just do list.Convert ALL (e => e.ToString ()). ToArray) `, chỉ cần gõ ít hơn.
David

chuỗi.Join (",", danh sách); sẽ làm tốt thôi :)
Christian

6

Nếu bất kỳ cơ quan nào muốn chuyển đổi danh sách các đối tượng lớp tùy chỉnh thay vì danh sách chuỗi thì ghi đè phương thức ToString của lớp của bạn bằng biểu diễn hàng csv của lớp.

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

Sau đó, mã sau đây có thể được sử dụng để chuyển đổi danh sách lớp này sang CSV với cột tiêu đề

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. Chọn thay thế MyClass.Select
Piotr Ferenc

5

Vì mã trong liên kết được cung cấp bởi @Frank Tạo tệp CSV từ Danh sách chung .NET, có một vấn đề nhỏ là kết thúc mọi dòng bằng một ,mã tôi đã sửa đổi mã để loại bỏ nó. Hy vọng nó sẽ giúp ai đó.

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

        }
    }
}

Thông tin bổ sung: Quá trình không thể truy cập tệp 'c: \ temp \ MatchMainWav.csv' vì nó đang được sử dụng bởi một quy trình khác. thư mục devtồn tại, nhưng không phải là tập tin ... tôi không sử dụng đúng không?
Tom Stickel

Phương thức File.Create tạo tệp và mở FileStream trên tệp. Vì vậy, tập tin của bạn đã được mở. Bạn hoàn toàn không cần tập tin. Phương pháp tạo tất cả:
David

Nếu bất kỳ thuộc tính nào là null, có cách nào khác không?
Daniel Jackson

@DanielJackson Bạn có thể viết một mệnh đề where trong tuyên bố này sw.Write(string.Join(",", props.Select(d => d.Name).ToArray()) + newLine);Không được kiểm tra nhưng không biết bạn đang cố gắng đạt được điều gì
Ali Umair

4

Tôi giải thích sâu về bài viết này . Tôi sẽ chỉ dán mã ở đây với các mô tả ngắn gọn.

Đây là phương pháp tạo hàng tiêu đề. Nó sử dụng tên thuộc tính làm tên cột.

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

Phương thức này tạo ra tất cả các hàng giá trị

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

Và đây là phương pháp kết hợp chúng lại với nhau và tạo tập tin thực tế.

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

1
Điều này hoạt động rất tốt. Tôi đã cải thiện điều này để vượt qua dấu phân cách như một tham số, vì vậy bất kỳ loại tệp được phân tách nào cũng có thể được tạo. CSV rất khó xử lý nếu văn bản có dấu phẩy, vì vậy tôi tạo |các tệp được phân tách bằng phiên bản cải tiến. Cảm ơn!
Shiva

3

Mọi giải pháp chỉ hoạt động nếu Liệt kê một danh sách (của chuỗi)

Nếu bạn có một danh sách chung về các Đối tượng của riêng bạn như danh sách (của ô tô) trong đó ô tô có n thuộc tính, bạn phải lặp lại PropertiesInfo của từng đối tượng ô tô.

Nhìn vào: http://www.csharptocsharp.com/generate-csv-from-generic-list


1
bạn không thể ghi đè ToString của lớp và sử dụng các phương thức trên?
Gabriel Guimarães

3

Tôi thích một phương pháp mở rộng đơn giản tốt đẹp

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

Sau đó, bạn chỉ có thể gọi phương thức trong danh sách ban đầu:

string CsvString = myList.ToCsv();

Sạch hơn và dễ đọc hơn một số gợi ý khác.


2

Vấn đề với String.Join là bạn không xử lý trường hợp dấu phẩy đã tồn tại trong giá trị. Khi dấu phẩy tồn tại thì bạn bao quanh giá trị trong Dấu ngoặc kép và thay thế tất cả Dấu ngoặc kép hiện có bằng Dấu ngoặc kép.

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

Xem mô-đun CSV


2

Thư viện CsvHelper rất phổ biến trong Nuget. Bạn xứng đáng, anh bạn! https://github.com/JoshClose/CsvHelper/wiki/Basics

Sử dụng CsvHelper thực sự dễ dàng. Cài đặt mặc định của nó được thiết lập cho các tình huống phổ biến nhất.

Đây là một ít dữ liệu thiết lập.

Diễn viên.csv:

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

Actor.cs (đối tượng lớp tùy chỉnh đại diện cho một diễn viên):

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

Đọc tệp CSV bằng CsvReader:

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

var diễn viênList = csv.GetRecords ();

Viết vào một tệp CSV.

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

2

Vì bất kỳ lý do gì, @AliUmair đã hoàn nguyên chỉnh sửa thành câu trả lời của anh ấy để sửa mã của anh ấy không chạy như hiện tại, vì vậy đây là phiên bản làm việc không có lỗi truy cập tệp và xử lý đúng các giá trị thuộc tính đối tượng null:

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

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

Trang web này đã thực hiện một số thử nghiệm rộng rãi về cách ghi vào tệp bằng cách sử dụng trình ghi đệm, đọc từng dòng dường như là cách tốt nhất, sử dụng trình tạo chuỗi là một trong những cách chậm nhất.

Tôi sử dụng các kỹ thuật của anh ấy rất nhiều để viết công cụ để tập tin nó hoạt động tốt.


1

Một phương thức mở rộng ToCsv () mục đích chung:

  • Hỗ trợ Int16 / 32/64, float, double, binary và bất cứ thứ gì hỗ trợ ToString ()
  • Tùy chọn phân tách tham gia tùy chọn
  • Bộ chọn tùy chỉnh tùy chọn
  • Thông số xử lý null / trống tùy chọn (quá tải Opt ())

Ví dụ sử dụng:

"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

Thực hiện

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

Đây là phương thức mở rộng của tôi, nó trả về một chuỗi cho đơn giản nhưng việc triển khai của tôi ghi tệp vào một hồ dữ liệu.

Nó cung cấp cho bất kỳ dấu phân cách nào, thêm dấu ngoặc kép vào chuỗi (trong trường hợp chúng chứa dấu phân cách) và các giao dịch sẽ vô hiệu và khoảng trống.

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

Sử dụng:

 // Tab Delimited (TSV)
 var csv = MyList.ToCsv<MyClass>("\t");
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.