65.9K
CodeProject 正在变化。 阅读更多。
Home

在 C# 中读写 CSV 文件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (60投票s)

2012 年 7 月 4 日

CPOL

2分钟阅读

viewsIcon

778025

这里提供一些轻量级的 C# 代码,用于读取和写入 CSV 文件。

引言

一个常见的需求是让应用程序与其他程序共享数据。虽然可以使用接口来处理例如 Microsoft Excel 数据文件,但这种方法通常比较复杂,涉及大量的开销,并且需要支持库与您的应用程序一起使用。

逗号分隔值 (CSV) 文件

让您的应用程序共享数据的更简单方法是读取和写入逗号分隔值 (CSV) 文件。CSV 文件可以被许多程序轻松读取和写入,包括 Microsoft Excel。

在大多数情况下,读取和写入 CSV 文件非常简单。顾名思义,CSV 文件只是一个纯文本文件,其中每行包含一个或多个由逗号分隔的值。每个值是一个字段(或电子表格中的列),每行是一个记录(或电子表格中的行)。

然而,这其中涉及一些额外的工作。双引号用于包围包含逗号的值,以防止逗号被解释为值分隔符。同样,对于包含双引号的值也这样做。此外,两个双引号一起表示值中的一个双引号,而不是值分隔符。

因此,这似乎是一个适合使用便捷的 C# 类来完成的完美任务。列表 1 显示了我的 CsvFileWriterCsvFileReader 类。 

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);
        }
    }
}
列表 1:CsvFileWriter 和 CsvFileReader 类

由于 .NET 流类通常分为读取和写入两种,因此我决定遵循这种模式,将我的 CSV 类分为 CsvFileWriterCsvFileReader。 这也简化了代码,因为两个类都不需要担心文件处于哪种模式,也不需要防止用户切换模式。

写入器类执行必要的任何编码,如上所述,而读取器类执行必要的任何解码。 

使用代码

两个类都接受一个 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();
        }
    }
}
列表 2:编写和读取 CSV 文件的示例代码

结论 

差不多就是这样了。 这些类相当简单,所以我没有包含示例项目。 类中的所有代码都显示在列表 1 中。 请注意,目前的代码没有处理跨多行的带引号的值。 

这段代码应该对任何想要以简单的方式与 Microsoft Excel 或任何其他可以读取或写入 CSV 文件的程序共享数据的人有所帮助。 

© . All rights reserved.