在 C# 中读写 CSV 文件






4.94/5 (60投票s)
这里提供一些轻量级的 C# 代码,用于读取和写入 CSV 文件。
引言
一个常见的需求是让应用程序与其他程序共享数据。虽然可以使用接口来处理例如 Microsoft Excel 数据文件,但这种方法通常比较复杂,涉及大量的开销,并且需要支持库与您的应用程序一起使用。
逗号分隔值 (CSV) 文件
让您的应用程序共享数据的更简单方法是读取和写入逗号分隔值 (CSV) 文件。CSV 文件可以被许多程序轻松读取和写入,包括 Microsoft Excel。
在大多数情况下,读取和写入 CSV 文件非常简单。顾名思义,CSV 文件只是一个纯文本文件,其中每行包含一个或多个由逗号分隔的值。每个值是一个字段(或电子表格中的列),每行是一个记录(或电子表格中的行)。
然而,这其中涉及一些额外的工作。双引号用于包围包含逗号的值,以防止逗号被解释为值分隔符。同样,对于包含双引号的值也这样做。此外,两个双引号一起表示值中的一个双引号,而不是值分隔符。
因此,这似乎是一个适合使用便捷的 C# 类来完成的完美任务。列表 1 显示了我的 CsvFileWriter
和 CsvFileReader
类。
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace ReadWriteCsv
{
/// <summary>
/// Class to store one CSV row
/// </summary>
public class CsvRow : List<string>
{
public string LineText { get; set; }
}
/// <summary>
/// Class to write data to a CSV file
/// </summary>
public class CsvFileWriter : StreamWriter
{
public CsvFileWriter(Stream stream)
: base(stream)
{
}
public CsvFileWriter(string filename)
: base(filename)
{
}
/// <summary>
/// Writes a single row to a CSV file.
/// </summary>
/// <param name="row">The row to be written</param>
public void WriteRow(CsvRow row)
{
StringBuilder builder = new StringBuilder();
bool firstColumn = true;
foreach (string value in row)
{
// Add separator if this isn't the first value
if (!firstColumn)
builder.Append(',');
// Implement special handling for values that contain comma or quote
// Enclose in quotes and double up any double quotes
if (value.IndexOfAny(new char[] { '"', ',' }) != -1)
builder.AppendFormat("\"{0}\"", value.Replace("\"", "\"\""));
else
builder.Append(value);
firstColumn = false;
}
row.LineText = builder.ToString();
WriteLine(row.LineText);
}
}
/// <summary>
/// Class to read data from a CSV file
/// </summary>
public class CsvFileReader : StreamReader
{
public CsvFileReader(Stream stream)
: base(stream)
{
}
public CsvFileReader(string filename)
: base(filename)
{
}
/// <summary>
/// Reads a row of data from a CSV file
/// </summary>
/// <param name="row"></param>
/// <returns></returns>
public bool ReadRow(CsvRow row)
{
row.LineText = ReadLine();
if (String.IsNullOrEmpty(row.LineText))
return false;
int pos = 0;
int rows = 0;
while (pos < row.LineText.Length)
{
string value;
// Special handling for quoted field
if (row.LineText[pos] == '"')
{
// Skip initial quote
pos++;
// Parse quoted value
int start = pos;
while (pos < row.LineText.Length)
{
// Test for quote character
if (row.LineText[pos] == '"')
{
// Found one
pos++;
// If two quotes together, keep one
// Otherwise, indicates end of value
if (pos >= row.LineText.Length || row.LineText[pos] != '"')
{
pos--;
break;
}
}
pos++;
}
value = row.LineText.Substring(start, pos - start);
value = value.Replace("\"\"", "\"");
}
else
{
// Parse unquoted value
int start = pos;
while (pos < row.LineText.Length && row.LineText[pos] != ',')
pos++;
value = row.LineText.Substring(start, pos - start);
}
// Add field to list
if (rows < row.Count)
row[rows] = value;
else
row.Add(value);
rows++;
// Eat up to and including next comma
while (pos < row.LineText.Length && row.LineText[pos] != ',')
pos++;
if (pos < row.LineText.Length)
pos++;
}
// Delete any unused items
while (row.Count > rows)
row.RemoveAt(rows);
// Return true if any columns read
return (row.Count > 0);
}
}
}
由于 .NET 流类通常分为读取和写入两种,因此我决定遵循这种模式,将我的 CSV 类分为 CsvFileWriter
和 CsvFileReader
。 这也简化了代码,因为两个类都不需要担心文件处于哪种模式,也不需要防止用户切换模式。
写入器类执行必要的任何编码,如上所述,而读取器类执行必要的任何解码。
使用代码
两个类都接受一个 CsvRow
参数。 CsvRow
类从 List<string>
派生,因此基本上只是一个字符串列表。
当您调用 CsvFileWriter.WriteRow()
时,行参数指定要写入的字符串值。 当您调用 CsvFileReader.ReadRow()
时,行参数返回读取的值。
CsvFileReader.ReadRow()
还将整行放入 CsvRow.LineText
成员中,以防调用者想要检查该行。 最后,CsvFileReader.ReadRow()
返回一个布尔值,当当前行无法读取任何值时,该值为 false
。 在正常情况下,这将指示文件结束。
列表 2 演示了如何使用这些类。
void WriteTest()
{
// Write sample data to CSV file
using (CsvFileWriter writer = new CsvFileWriter("WriteTest.csv"))
{
for (int i = 0; i < 100; i++)
{
CsvRow row = new CsvRow();
for (int j = 0; j < 5; j++)
row.Add(String.Format("Column{0}", j));
writer.WriteRow(row);
}
}
}
void ReadTest()
{
// Read sample data from CSV file
using (CsvFileReader reader = new CsvFileReader("ReadTest.csv"))
{
CsvRow row = new CsvRow();
while (reader.ReadRow(row))
{
foreach (string s in row)
{
Console.Write(s);
Console.Write(" ");
}
Console.WriteLine();
}
}
}
结论
差不多就是这样了。 这些类相当简单,所以我没有包含示例项目。 类中的所有代码都显示在列表 1 中。 请注意,目前的代码没有处理跨多行的带引号的值。
这段代码应该对任何想要以简单的方式与 Microsoft Excel 或任何其他可以读取或写入 CSV 文件的程序共享数据的人有所帮助。