如何自动将IEnumerable <t>转换为csv文件?</t>

时间:2012-06-06 14:05:36

标签: c# asp.net

我正在尝试创建一个接受任何通用列表的用户控件,以便我可以遍历它并创建CSV导出。是否可以公开可以接受任何类型的公共财产? (即List<Product>List<Customer>等。如果是,怎么做?

public IEnumerable<T> AnyList { get; set; }

就我的实用方法而言,这就是我所拥有的:

public static byte[] ToCsv<T>(string separator, IEnumerable<T> objectlist)
{
    //Type t = typeof(T); Deleted this line.

    //Here's the line of code updated.
    PropertyInfo[] propertyNames = objectlist.First().GetType().GetProperties();

    string header = String.Join(separator, propertyNames.Select(f => f.Name).ToArray());

    StringBuilder csvdata = new StringBuilder();
    csvdata.AppendLine(header);

    foreach (var o in objectlist)
        csvdata.AppendLine(ToCsvFields(separator, propertyNames, o));

    return Encoding.ASCII.GetBytes(csvdata.ToString());
}

public static string ToCsvFields(string separator, PropertyInfo[] fields, object o)
{
    StringBuilder linie = new StringBuilder();

    foreach (var f in fields)
    {
        if (linie.Length > 0)
            linie.Append(separator);

        var x = f.GetValue(o, null);

        if (x != null)
            linie.Append(x.ToString());
    }

    return linie.ToString();
}

6 个答案:

答案 0 :(得分:16)

如果您想要从对象列表中“创建CSV导出”,您应该使用反射来计算列。

最新更新2016年2月12日:

将分隔符默认设置为逗号更有意义,并且有助于使初始标题行的输出可选。现在,它还使用Concat支持字段简单属性

public static IEnumerable<string> ToCsv<T>(IEnumerable<T> objectlist, string separator = ",", bool header = true)
{
    FieldInfo[] fields = typeof(T).GetFields();
    PropertyInfo[] properties = typeof(T).GetProperties();
    if (header)
    {
        yield return String.Join(separator, fields.Select(f => f.Name).Concat(properties.Select(p=>p.Name)).ToArray());
    }
    foreach (var o in objectlist)
    {
        yield return string.Join(separator, fields.Select(f=>(f.GetValue(o) ?? "").ToString())
            .Concat(properties.Select(p=>(p.GetValue(o,null) ?? "").ToString())).ToArray());
    }
}

所以你可以像这样用逗号分隔:

foreach (var line in ToCsv(objects))
{
    Console.WriteLine(line);
}

或类似于另一个分隔符(例如TAB):

foreach (var line in ToCsv(objects, "\t"))
{
    Console.WriteLine(line);
}

实际例子

将列表写入以逗号分隔的CSV文件

using (TextWriter tw = File.CreateText("C:\testoutput.csv"))
{
    foreach (var line in ToCsv(objects))
    {
        tw.WriteLine(line);
    }
}

或以制表符分隔

using (TextWriter tw = File.CreateText("C:\testoutput.txt"))
{
    foreach (var line in ToCsv(objects, "\t"))
    {
        tw.WriteLine(line);
    }
}

如果您有复杂的字段/属性,则需要将它们从select子句中过滤掉。


以前的更新

最后的想法首先(所以不会错过):

如果您更喜欢通用解决方案(这可确保对象属于同一类型):

public static IEnumerable<string> ToCsv<T>(string separator, IEnumerable<T> objectlist)
{
    FieldInfo[] fields = typeof(T).GetFields();
    PropertyInfo[] properties = typeof(T).GetProperties();
    yield return String.Join(separator, fields.Select(f => f.Name).Union(properties.Select(p=>p.Name)).ToArray());
    foreach (var o in objectlist)
    {
        yield return string.Join(separator, fields.Select(f=>(f.GetValue(o) ?? "").ToString())
            .Union(properties.Select(p=>(p.GetValue(o,null) ?? "").ToString())).ToArray());
    }
}

这包括公共领域和公共财产。

通常使用反射,您不需要知道列表中对象的类型(您必须假设它们都是相同的类型)。

你可以使用:

public IEnumerable<object> AnyList { get; set; }

您想要做的事情的基本过程如下:

  • 从列表中的第一个对象获取类型(例如GetType())。
  • 迭代该类型的属性。
  • 写出CSV标题,例如基于属性(或属性)的名称。
  • 对于列表中的每个项目......
    • 迭代该类型的属性
    • 获取每个属性的值(作为对象)
    • 使用分隔符
    • 写出对象的ToString()版本

您可以使用相同的算法生成2D数组(即,如果您希望控件以表格/网格形式显示CSV)。

您遇到的唯一问题可能是从IEnumerables /特定类型的列表转换为IEnumerable。在这些情况下,只需在特定类型的枚举上使用.Cast<object>

更新

当您使用http://www.joe-stevens.com/2009/08/03/generate-a-csv-from-a-generic-list-of-objects-using-reflection-and-extension-methods/

中的代码时

您需要对其代码进行以下更改:

// Make it a simple extension method for a list of object
public static string GetCSV(this List<object> list)
{
    StringBuilder sb = new StringBuilder();

    //Get the properties from the first object in the list for the headers
    PropertyInfo[] propInfos = list.First().GetType().GetProperties();

如果您想支持一个空列表,请添加第二个参数(例如Type type),这是您期望的 对象的类型,并使用它而不是list.First()。GetType ()。

注意:我没有在其代码中看到引用T的其他任何地方,但如果我错过了它,编译器会找到它:)

更新(完整和简化的CSV生成器):

public static IEnumerable<string> ToCsv(string separator, IEnumerable<object> objectlist)
{
    if (objectlist.Any())
    {
        Type type = objectlist.First().GetType();
        FieldInfo[] fields = type.GetFields();
        yield return String.Join(separator, fields.Select(f => f.Name).ToArray());
        foreach (var o in objectlist)
        {
            yield return string.Join(separator, fields.Select(f=>(f.GetValue(o) ?? "").ToString()).ToArray());
        }
    }
}

这样做的好处是内存开销很低,因为它会在结果发生时产生结果,而不是创建一个庞大的字符串。您可以像使用它一样使用它:

foreach (var line in ToCsv(",", objects))
{
    Console.WriteLine(line);
}

我更喜欢实践中的通用解决方案,所以首先放置它。

答案 1 :(得分:2)

你必须做这样的事情:

public class MyUserControl<T> : UserControl
{
    public IEnumerable<T> AnyList { get; set; }
}

从C#视图来看,这非常好。但是,如果您希望能够在ASPX标记中声明用户控件,则用户控件不能包含类级别类型参数。这可能:

<controls:MyUserControl<Customer> runat="server" ID="Foo" />

现在,如果您的用户控件只会在代码隐藏中创建,而不是在标记中创建,那么这很好:

this.SomePlaceHolder.Controls.Add(new MyUserControl<Customer>());

如果您需要/希望能够在ASPX标记中声明您的用户控件,那么您可以做的最好的事情是创建IEnumerable<object>属性。

答案 2 :(得分:1)

我从here

找到了一个很好的解决方案
public static class LinqToCSV
{
    public static string ToCsv<T>(this IEnumerable<T> items)
        where T : class
    {
        var csvBuilder = new StringBuilder();
        var properties = typeof(T).GetProperties();
        foreach (T item in items)
        {
            string line = string.Join(",",properties.Select(p => p.GetValue(item, null).ToCsvValue()).ToArray());
            csvBuilder.AppendLine(line);
        }
        return csvBuilder.ToString();
    }

    private static string ToCsvValue<T>(this T item)
    {
        if(item == null) return "\"\"";

        if (item is string)
        {
            return string.Format("\"{0}\"", item.ToString().Replace("\"", "\\\""));
        }
        double dummy;
        if (double.TryParse(item.ToString(), out dummy))
        {
            return string.Format("{0}", item);
        }
        return string.Format("\"{0}\"", item);
    }
}

显然它处理逗号和双引号。我要用它。

答案 3 :(得分:0)

要拥有通用属性,该类也必须是通用的:

public class MyClass<T>
{
   public IEnumerable<T> AnyList { get; set; }
}

答案 4 :(得分:0)

由于IEnumerableIEnumerable<T>的基类,因此任何正确编写的IEnumerable<T>类都可以传递给IEnumerable类型的属性。因为听起来你只对“ToString()部分”感兴趣,而不是“T部分”,IEnumerable应该可以正常工作。

答案 5 :(得分:0)

这对我有用。

using System.Reflection;
using System.Text;
//***    


public void ToCSV<T>(IEnumerable<T> items, string filePath)
{
    var dataTable = new DataTable(typeof(T).Name);
    PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
    foreach (var prop in props)
        dataTable.Columns.Add(prop.Name, prop.PropertyType);

    foreach (var item in items)
    {
        var values = new object[props.Length];
        for (var i = 0; i < props.Length; i++)
        {
            values[i] = props[i].GetValue(item, null);
        }
        dataTable.Rows.Add(values);
    }

    StringBuilder fileContent = new StringBuilder();
    foreach (var col in dataTable.Columns)
        fileContent.Append(col.ToString() + ",");

    fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1);

    foreach (DataRow dr in dataTable.Rows)
    {
        foreach (var column in dr.ItemArray)
            fileContent.Append("\"" + column.ToString() + "\",");

        fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1);
    }

    try
    {
        System.IO.File.WriteAllText(filePath, fileContent.ToString());
    }
    catch (Exception)
    {

        throw;
    }
}