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

Rod*_*Rod 9 c# asp.net

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

public IEnumerable<T> AnyList { get; set; }
Run Code Online (Sandbox Code Playgroud)

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

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();
}
Run Code Online (Sandbox Code Playgroud)

Gon*_*ing 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());
    }
}
Run Code Online (Sandbox Code Playgroud)

所以你这样用它来逗号分隔:

foreach (var line in ToCsv(objects))
{
    Console.WriteLine(line);
}
Run Code Online (Sandbox Code Playgroud)

或像这样的另一个分隔符(例如TAB):

foreach (var line in ToCsv(objects, "\t"))
{
    Console.WriteLine(line);
}
Run Code Online (Sandbox Code Playgroud)

实际例子

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

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

或者以制表符分隔

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

如果您有复杂的字段/属性,则需要将它们从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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

你可以使用:

public IEnumerable<object> AnyList { get; set; }
Run Code Online (Sandbox Code Playgroud)

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

  • 从列表中的第一个对象获取类型(例如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-方法/

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

// 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();
Run Code Online (Sandbox Code Playgroud)

如果要支持空列表,请添加第二个参数(例如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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这具有低内存开销的优点,因为它在发生时产生结果,而不是创建大量字符串.您可以像以下一样使用它:

foreach (var line in ToCsv(",", objects))
{
    Console.WriteLine(line);
}
Run Code Online (Sandbox Code Playgroud)

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

  • 当你把别人的代码从架子上拿走时会发生这种情况:)我会用他的代码的修改版本来更新答案,但基本上只需要进行一些小改动. (2认同)