序列化对象的自定义字符串格式用于输出文件的最佳实践
-
19-09-2019 - |
题
我正要执行,以产生一个Excel友好的格式写入到一个输出文件,这将在后面拾取和处理上的特定业务类的ToString()的重写。下面是该数据应该是这样的:
5555555 "LASTN SR, FIRSTN" 5555555555 13956 STREET RD TOWNSVILLE MI 48890 25.88 01-003-06-0934
这没什么大不了的,我只是做一个格式字符串和覆盖ToString()
,但会改变ToString()
的行为,我决定序列化这样的任何物体,使得ToString()
执行所有衣衫褴褛跨库。
现在,我已经在的IFormatProvider 读了,而一个类实现这听起来像一个好主意,但我还是有点困惑,所有这样的逻辑应该存在以及如何建立格式化程序类。
做什么,当你需要做一个CSV,制表符分隔或其他一些非XML任意字符串出对象的,你们怎么办?
解决方案
下面是一种通用的方式用于从对象列表创建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();
}
许多变化可以由,如在ToCsv直接写入到一个文件(),或者与一个IEnumerable和产量语句替换StringBuilder的。
其他提示
下面是每Hejndorf的CSV想法的简化版本(没有内存开销,因为它产生每行依次)。由于普遍的需求也通过使用Concat
的同时支持字段和简单属性。
更新2017年5月18日
这个例子从来没有打算成为一个完整的解决方案,只是推进张贴每Hejndorf了原来的想法。为了产生有效的CSV需要更换任何文字分隔符,在文本中,有2个分隔符字符的序列。例如一个简单的.Replace("\"", "\"\"")
。
更新2016年2月12日
今天再次使用自己的代码中的一个项目后,我意识到我不应该采取什么是理所当然的,当我从@Per Hejndorf
的例子开始。更有意义的假设“”(逗号)的缺省的定界符,使分隔第二,可选的的,参数。我自己的库版本还提供了控制标题行是否应该返回,因为有时候你只需要数据第3 header
参数。
e.g。
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);
}
}
如果您有复杂的字段/属性,你将需要过滤出来的选择条款。的
以前的版本和细节如下:
下面是每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());
}
}
您可以遍历它是这样的:强>
foreach (var line in ToCsv(",", objects))
{
Console.WriteLine(line);
}
其中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());
}
}
作为一个经验我主张只重写的toString作为调试工具的规则,如果它是业务逻辑应该是类/接口上的显式方法。
对于这样简单的序列我建议有一个单独的类,它知道你的CSV输出库和你的业务对象,做序列化,而不是推到系列化的业务对象本身。
此方式你最终产生的模型的视图,每个输出格式的类。
对于更复杂的序列化,你正在试图写出一个对象图的持久性我会考虑把它在业务类 - 但只有当它使更清晰的代码
与我迄今发现的解决方案的问题是,他们不会让你导出属性的子集,但只有整个对象。大多数时候,当我们需要的数据在CSV出口,我们需要一个精确的方式“量体裁衣”的格式,所以我通过传递的类型Func<T, string>
的参数数组来创建这个简单的扩展方法,可以让我做指定映射。
public static string ToCsv<T>(this IEnumerable<T> list, params Func<T, string>[] properties)
{
var columns = properties.Select(func => list.Select(func).ToList()).ToList();
var stringBuilder = new StringBuilder();
var rowsCount = columns.First().Count;
for (var i = 0; i < rowsCount; i++)
{
var rowCells = columns.Select(column => column[i]);
stringBuilder.AppendLine(string.Join(",", rowCells));
}
return stringBuilder.ToString();
}
<强>用法:强>
philosophers.ToCsv(x => x.LastName, x => x.FirstName)
<强>生成:强>
Hayek,Friedrich
Rothbard,Murray
Brent,David
我有一个问题的高科技型魔法的变化是两个属性具有相同的值,只有一个会得到填充。这似乎已经固定它:
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, (properties.Select(p => (p.GetValue(o, null) ?? "").ToString())).ToArray());
}
}
飘编码的回答是非常有益的。我为了处理文本小鬼将软管输出作了一些修改它。
/******************************************************/
public static IEnumerable<string> ToCsv<T>(IEnumerable<T> objectlist, string separator = ",", bool header = true)
{
FieldInfo[] fields = typeof(T).GetFields();
PropertyInfo[] properties = typeof(T).GetProperties();
string str1;
string str2;
if(header)
{
str1 = String.Join(separator, fields.Select(f => f.Name).Concat(properties.Select(p => p.Name)).ToArray());
str1 = str1 + Environment.NewLine;
yield return str1;
}
foreach(var o in objectlist)
{
//regex is to remove any misplaced returns or tabs that would
//really mess up a csv conversion.
str2 = string.Join(separator, fields.Select(f => (Regex.Replace(Convert.ToString(f.GetValue(o)), @"\t|\n|\r", "") ?? "").Trim())
.Concat(properties.Select(p => (Regex.Replace(Convert.ToString(p.GetValue(o, null)), @"\t|\n|\r", "") ?? "").Trim())).ToArray());
str2 = str2 + Environment.NewLine;
yield return str2;
}
}