使用反射构建通用 CSV 写入器/读取器
介绍
(逗号分隔值)CSV 文件格式是一种非常常见的以简单且可移植的格式存储数据集的方法。此外,由于它是纯文本,因此在应用程序中以编程方式创建此类文件非常容易。有一些库可用于此类任务,但如果您想自定义或创建特定内容,最好编写自己的实现以实现完全控制。
在本指南中,我将解释如何编写通用的 CSV 编写器/阅读器,它将自动从输入对象的公共属性中选择数据并生成 CSV 文件。
在我们深入研究代码之前,让我解释一下什么是 CSV 文件格式,以便我们更好地理解我们正在处理的内容。
什么是 CSV 文件?
CSV 文件是一种纯文本文件,以表格形式保存数据。每行相当于表格中的一行,列之间用逗号分隔。因此,CSV 文件的名称为逗号分隔值。
下面是一个包含人员数字、名字和姓氏的表格的文件示例:
1,murat,aykanat
2,john,smith
CSV 格式非常有用,因为它是一种文本文件,任何操作系统都可以读取它。例如,如果您的应用程序在 Windows 计算机上创建了 CSV 文件,您可以在 Linux 计算机上打开并使用它。因此,它是一种非常便携且易于阅读的文件格式。
CSV 文件的格式
分隔符问题
在某些情况下,文件可能不是以逗号分隔的。特别是如果您使用的是第三方程序(例如 Microsoft Excell),则根据您机器的文化,“分隔符”可能是不同的字符,例如;。这是因为不同文化中的小数分隔符不同。在某些文化中,小数分隔符是. ,因此 CSV 分隔符可以是,。但在某些文化中,小数分隔符是,因此 CSV 文件必须使用;作为分隔符。
例如,如果您的语言环境设置为某种欧洲文化,如fr-FR,则默认小数点分隔符变为,并且您需要在 CSV 文件中使用;作为列分隔符:
3,5;2,5;5,4
4,5;6,7;8,9
但是在默认设置为en-US 的机器中,由于小数点分隔符默认为.,因此相同的 CSV 文件将如下所示:
3.5,2.5,5.4
4.5,6.7,8.9
每行数据字段数
最重要的规则是每一行必须包含相同数量的数据字段,否则任何 CSV 读取器都无法读取,而且也没有意义。
如果您有一个空数据字段,那么您只需使用一个空字符串即可。
1,,aykanat
2,john,smith
数据字段中的逗号
如果您的 CSV 文件中有文本值,您可能会遇到一个问题:其中某一行内有一个逗号,这会带来问题,因为该字段会与该逗号分开,并且最终会在该行中出现一个额外的列。
1, Hello, world!
在上面的例子中,我们的第一列是 1 ,第二列是Hello, world!,但是,CSV 读取器会将该行分成 3 列1、Hello和world!。
为了解决这个问题,我们必须使用引号:
1, "Hello, world!"
这样,我们的意思是字符串“Hello, world!”是一个单一数据字段。
您也可以在单个字符串上使用引号,但这不是必须的。
1,"murat","aykanat"
1,"john","smith"
数据字段中的引号
我们还可以在数据字段中使用实际的引号。在这种情况下,我们需要将引号加倍,以表明它包含在数据字段中。
1,murat,""aykanat""
2,john,""smith""
其读法为:数字为1,名字为murat,姓氏为“aykanat”。
标头
您还可以向列添加标题。
id,name,lastname
1,murat,aykanat
1,john,smith
当列的数据字段不明确时,这很有用。例如,如果列都是数字,而您将文件发送给不知道列含义的人,那么对方会感到非常困惑,因为他或她不知道这些数字的含义。因此,在这些情况下,我们最好添加标题来指示这些值列的含义。
更多细节
如果您想了解有关 CSV 文件格式的更多信息,您可以使用有关 CSV 及其资源的维基百科文章。
理论
我们的想法很简单,我们想将一个对象数组输入到我们的 CSV Writer 中并输出一个 CSV 文件。对于读取部分,我们想输入文件路径并将一个对象数组输出回内存。
输入
public class Person
{
public int Id{ get; set };
public string Name { get; set; }
public string Lastname { get; set; }
}
输出
1,murat,aykanat
2,john,smith
然而,还有一些注意事项:
- CSV 写入器和读取器必须遵守上述 CSV 文件格式的规则
- 写入过程必须自动化。我的意思是,如果我们在一个类中有两个公共属性,那么将这些属性写入文件相当容易。但如果我们有 100 个或 10000 个公共属性怎么办?我们无法一个一个地写入它们。
- 每个对象都应该通过一种方法输出其公共属性。
可选注意事项:
- 由于一些 CSV 阅读器不支持 UTF-8 编码,因此必须将所有“特殊”字符转换为 ASCII 对应字符(如果可能)。请注意,在某些语言中可能无法做到这一点,但由于我将以土耳其语特殊字符为例,因此在本指南中可以做到这一点。
- 除非明确用引号定义,否则必须删除左右空格,因为根据我的经验,不必要的左右空格通常是输入值的人的错误,尤其是当这些值是从其他应用程序复制过来时。如果我们确实需要左右空格,我们可以使用引号。
- 可以通过属性的索引或名称来忽略它们。
CSV 编写器
根据我们上面的考虑,我们需要为每个要转换为 CSV 格式的类提供一种方法ToCsv() 。这可以通过 3 种方式实现:
- 使用方法ToCsv()实现ICsvable接口
- 重写ToString()
- 具有虚拟ToCsv()方法的抽象基类CsvableBase
我不想使用ToString()重写,因为也许我们在代码中的其他地方需要它,我希望ToCsv()是一个单独的方法,这样它的作用就一目了然了。此外,由于每个类的代码都相同,我将采用抽象类方式。但是,如果您的特定类已经从某个基类继承,则必须使用接口实现,因为您不能从 C# 中的多个基类继承。您只需为每个要转换为 CSV 的类复制和粘贴即可。
基本实现
这里我们需要做的是使用反射来获取类的所有公共属性的值。
public abstract class CsvableBase
{
public virtual string ToCsv()
{
string output = "";
var properties = GetType().GetProperties();
for (var i = 0; i < properties.Length; i++)
{
output += properties[i].GetValue(this).ToString();
if (i != properties.Length - 1)
{
output += ",";
}
}
return output;
}
}
所以我们在这里做的事情很简单:
- 以PropertyInfo集合的形式获取此类的所有公共属性。
- 对其进行迭代,获取它们的值并将其添加到输出中并添加逗号。
- 如果我们到达结尾,就不要加逗号。
让我们在控制台应用程序中对之前创建的Person类进行测试。
public class Person : CsvableBase
{
public Person(int id, string name, string lastname)
{
Id = id;
Name = name;
Lastname = lastname;
}
public int Id { get; set; }
public string Name { get; set; }
public string Lastname { get; set; }
}
class Program
{
static void Main(string[] args)
{
var p = new Person(1,"murat","aykanat");
Console.WriteLine(p.ToCsv());
Console.ReadLine();
}
}
上述代码的输出:
1,murat,aykanat
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~