将对象序列化为自定义字符串格式以在输出文件中使用的最佳做法

Chr*_*all 29 c# serialization formatprovider

我正准备在特定的业务类上实现ToString()的覆盖,以便生成一个Excel友好的格式来写入输出文件,稍后将对其进行拾取和处理.这是数据应该是什么样子:

5555555 "LASTN SR, FIRSTN"  5555555555  13956 STREET RD     TOWNSVILLE  MI  48890   25.88   01-003-06-0934
Run Code Online (Sandbox Code Playgroud)

仅仅创建一个格式字符串并覆盖它对我来说没什么大不了的ToString(),但是这将改变ToString()我决定以这种方式序列化的任何对象的行为,使得所有对象的实现ToString()都在整个库中.

现在,我一直在阅读IFormatProvider,并且实现它的类听起来是个好主意,但我仍然对所有这些逻辑应该驻留的位置以及如何构建格式化程序类感到困惑.

当你需要从对象中制作CSV,制表符分隔或其他非XML任意字符串时,你们会怎么做?

小智 67

以下是使用反射从对象列表创建CSV的通用方式:

public static string ToCsv<T>(string separator, IEnumerable<T> objectlist)
{
    Type t = typeof(T);
    FieldInfo[] fields = t.GetFields();

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

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

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

    return csvdata.ToString();
}

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

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

        var x = f.GetValue(o);

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

    return linie.ToString();
}
Run Code Online (Sandbox Code Playgroud)

可以进行许多变化,例如直接写入ToCsv()中的文件,或者用IEnumerable和yield语句替换StringBuilder.

  • 虽然objectList非常庞大,但您的解决方案速度很慢,并且可能存在内存不足问题. (2认同)

Gon*_*ing 33

这是Per Hejndorf的CSV概念的简化版本(没有内存开销,因为它依次产生每一行).由于受欢迎的需求,它还通过使用支持字段和简单属性Concat.

2017年5月18日更新

这个例子从来没有打算成为一个完整的解决方案,只是推进了Per Hejndorf发布的原始想法.要生成有效的CSV,您需要使用2个分隔符字符序列替换文本中的任何文本分隔符.例如一个简单的.Replace("\"", "\"\"").

2016年2月12日更新

在今天的项目中再次使用我自己的代码后,我意识到当我从示例开始时,我不应该把任何事情视为理所当然@Per Hejndorf.假设默认分隔符","(逗号)并使分隔符成为第二个可选参数更有意义.我自己的库版本还提供了第3个header参数,用于控制是否应返回标题行,因为有时您只需要数据.

例如

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子句中过滤掉.


以前的版本和细节如下:

这是Per Hejndorf的CSV概念的简化版本(没有内存开销,因为它依次产生每一行)并且只有4行代码:)

public static IEnumerable<string> ToCsv<T>(string separator, IEnumerable<T> objectlist)
{
    FieldInfo[] fields = typeof(T).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)

其中objects是强类型的对象列表.

此变体包括公共字段和简单公共属性:

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).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)

  • 感谢这个优雅的解决方案.对于使用此功能的其他任何人,您可能希望将Union替换为Concat,以便字段在相同时不会合并 (3认同)

Tom*_*Tom 8

根据经验,我主张只重写toString作为调试工具,如果是业务逻辑,它应该是类/接口上的显式方法.

对于像这样的简单序列化,我建议有一个单独的类,它知道您的CSV输出库和执行序列化的业务对象,而不是将序列化推送到业务对象本身.

这样,您最终会得到每个输出格式的类,从而生成模型视图.

对于更复杂的序列化,你试图写出一个持久性的对象图,我会考虑把它放在业务类中 - 但只有它能使代码更清晰.