在 C# 中解析 CSV 文件,带标头

Parsing CSV files in C#, with header

提问人:David Pfeffer 提问时间:1/17/2010 最后编辑:smciDavid Pfeffer 更新时间:4/29/2023 访问量:468448

问:

是否有默认/官方/推荐的方法来解析 C# 中的 CSV 文件?我不想滚动我自己的解析器。

此外,我还看到有人使用 ODBC/OLE DB 通过文本驱动程序读取 CSV,很多人不鼓励这样做,因为它有“缺点”。这些缺点是什么?

理想情况下,我正在寻找一种方法,通过该方法,我可以使用列名称读取CSV,使用第一条记录作为标题/字段名称。给出的一些答案是正确的,但基本上是将文件反序列化为类。

C# CSV 文件-IO IO 标头

评论


答:

152赞 marc_s 1/17/2010 #1

让图书馆为您处理所有细节!:-)

查看 FileHelpers 并保持干燥 - 不要重复自己 - 无需重新发明轮子无数次......

基本上,您只需要通过公共类(以及经过深思熟虑的属性,如默认值、NULL 值的替换等)来定义数据的形状 - CSV 中各个行中的字段,将 FileHelpers 引擎指向文件,然后宾果游戏 - 您可以从该文件中取回所有条目。一个简单的操作 - 出色的性能!

评论

1赞 mikus 8/12/2013
直到您需要真正自定义(无论如何,其中大部分都可以作为扩展实现)FileHelpers 是迄今为止最好的方法,非常方便、经过测试且性能良好的解决方案
4赞 Sudhanshu Mishra 6/1/2015
截至 2015 年 6 月 1 日,我下载 FileHelpers 的唯一方法是在 sourceforge.net 上搜索它。以下是使用的链接: sourceforge.net/projects/filehelpers/?source=directory
2赞 Marcos Meli 7/23/2015
@dotnetguy我们正在发布 3.1(目前为 3.1-rc2)的途中。此外,我们重新设计了网站:www.filehelpers.net 您可以从那里下载最新版本
1赞 Sudhanshu Mishra 7/23/2015
@MarcosMeli非常感谢!我已经在我的一个项目中使用了 FileHelpers,使用起来轻而易举 - 向团队致敬。我计划很快写一个博客,顺便说一句 - 喜欢这个新网站 - 干得好!
3赞 Alastair Maw 9/20/2019
FileHelpers 无法正确处理 CSV 中带引号的逗号,也无法实际映射字段标题,而是期望列的顺序与类型中声明字段的顺序相同。就我个人而言,我不会使用它。
12赞 2 revs, 2 users 86%Giorgi #2

如果你只需要读取csv文件,那么我推荐这个库:一个快速的CSV阅读器,
如果你还需要生成csv文件,那么使用这个文件:FileHelpers

它们都是免费和开源的。

评论

0赞 AnneTheAgile 10/6/2012
FileHelpers有一个吸引人的摘要: filehelpers.com FileHelpers 是一个免费且易于使用的 .NET 库,用于从文件、字符串或流中的固定长度或分隔记录导入/导出数据。
1赞 WhatsThePoint 10/24/2018
虽然此链接可能会回答问题,但在 Stack Overflow 上不鼓励仅链接答案,您可以通过获取链接的重要部分并将其放入您的答案中来改进此答案,这可确保您的答案在链接被更改或删除时仍然是答案:)
33赞 alexn 1/17/2010 #3

在业务应用程序中,我使用 codeproject.com 上的开源项目 CSVReader

它运行良好,并且具有良好的性能。在我提供的链接上有一些基准测试。

一个简单的例子,从项目页面复制:

using (CsvReader csv = new CsvReader(new StreamReader("data.csv"), true))
{
    int fieldCount = csv.FieldCount;
    string[] headers = csv.GetFieldHeaders();

    while (csv.ReadNextRecord())
    {
        for (int i = 0; i < fieldCount; i++)
            Console.Write(string.Format("{0} = {1};", headers[i], csv[i]));

        Console.WriteLine();
    }
}

正如你所看到的,它非常容易使用。

258赞 Josh Close 2/1/2012 #4

CsvHelper(我维护的一个库)会将 CSV 文件读入自定义对象。

using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
    var records = csv.GetRecords<Foo>();
}

有时你并不拥有你试图阅读的对象。在这种情况下,可以使用 Fluent 映射,因为不能将属性放在类上。

public sealed class MyCustomObjectMap : CsvClassMap<MyCustomObject>
{
    public MyCustomObjectMap()
    {
        Map( m => m.Property1 ).Name( "Column Name" );
        Map( m => m.Property2 ).Index( 4 );
        Map( m => m.Property3 ).Ignore();
        Map( m => m.Property4 ).TypeConverter<MySpecialTypeConverter>();
    }
}

评论

22赞 Gromer 9/7/2012
我同意@kubal5003。卖给我的是你把它作为 NuGet 包提供。谢谢伙计,它很快,并且可以完成我需要的所有 csv 阅读。
9赞 marisks 1/16/2013
太快了。在 10 秒内读取和反序列化 130 万条记录。
3赞 Marko 8/6/2013
很棒的库,非常容易实现。我只是建议 Josh 在这里更新他的答案,因为自从编写这个答案以来,库发生了一些变化,您不能再实例化 CsvHelper(它现在只是一个命名空间),但您必须使用 CsvReader 类。
3赞 norgie 10/21/2017
knocte,它现在被称为 ClassMap。还有其他变化,比如在请求标头记录之前必须进行读取(顺便说一下,它被设置为第一次调用 Read()时读取的任何内容)。就像其他人之前提到的那样,它速度超快且易于使用。
6赞 Josh Close 2/12/2020
CsvHelper 也不会将所有行加载到内存中。它加载一个小缓冲区并产生结果。您可能还想提及 SoftCircuits.CsvParser 由您维护。我相信这是现在对 SO 的政策。
14赞 Base33 3/30/2012 #5

这是我经常使用的帮助程序类,以防有人回到这个线程(我想分享它)。

我使用它是为了简单地将其移植到可供使用的项目中:

public class CSVHelper : List<string[]>
{
  protected string csv = string.Empty;
  protected string separator = ",";

  public CSVHelper(string csv, string separator = "\",\"")
  {
    this.csv = csv;
    this.separator = separator;

    foreach (string line in Regex.Split(csv, System.Environment.NewLine).ToList().Where(s => !string.IsNullOrEmpty(s)))
    {
      string[] values = Regex.Split(line, separator);

      for (int i = 0; i < values.Length; i++)
      {
        //Trim values
        values[i] = values[i].Trim('\"');
      }

      this.Add(values);
    }
  }
}

并像以下方式使用它:

public List<Person> GetPeople(string csvContent)
{
  List<Person> people = new List<Person>();
  CSVHelper csv = new CSVHelper(csvContent);
  foreach(string[] line in csv)
  {
    Person person = new Person();
    person.Name = line[0];
    person.TelephoneNo = line[1];
    people.Add(person);
  }
  return people;
}

[更新了 csv 帮助程序:修复了最后一个新行字符创建新行的错误]

评论

19赞 hakan 6/12/2012
如果任何 CSV 条目包含逗号 (,),则此代码将不起作用。
0赞 Base33 6/13/2012
为了保持轻量级,我使用管道字符作为分隔符。'|'
0赞 Cocoa Dev 1/23/2013
优秀的解决方案。只是一个关于第二个片段的问题。Person 是什么类型的对象
0赞 Base33 1/24/2013
@CocoaDev 它是一个包含两个字符串属性的类 - Name 和 TelephoneNo。不过,纯粹是为了这个例子。如果任何属性是整数,它应该只是一个直接的转换(带检查?
21赞 user1131926 5/21/2012 #6

我知道有点晚了,但刚刚找到了一个具有处理 csv 文件的类的库。Microsoft.VisualBasic.FileIOTextFieldParser

评论

1赞 AnneTheAgile 10/6/2012
使用该 api 的示例;msdn.microsoft.com/en-us/library/cakac7e6(v=vs.90).aspx
461赞 Alex from Jitbit 12/11/2013 #7

CSV 分析器现在是 .NET Framework 的一部分。

添加对 Microsoft.VisualBasic.dll 的引用(在 C# 中工作正常,不介意名称)

using (TextFieldParser parser = new TextFieldParser(@"c:\temp\test.csv"))
{
    parser.TextFieldType = FieldType.Delimited;
    parser.SetDelimiters(",");
    while (!parser.EndOfData)
    {
        //Process row
        string[] fields = parser.ReadFields();
        foreach (string field in fields)
        {
            //TODO: Process field
        }
    }
}

文档在这里 - TextFieldParser 类

P.S. 如果您需要 CSV 导出器,请尝试 CsvExport(discl:我是贡献者之一)

评论

4赞 MBoros 3/20/2014
根据我的经验,TextFieldParser 在处理大型(例如 > 250Mb)文件时表现不佳。:(
7赞 neural5torm 4/20/2016
在构造函数中,您可能希望使用与默认编码不同的编码,如下所示:new TextFieldParser(“c:\temp\test.csv”, System.Text.Encoding.UTF8)
2赞 Aditya 4/11/2017
如何指定 csv 文件是否包含标头?
5赞 5/2/2018
有没有办法在 .NET Core 中获取它?
7赞 z33k 12/11/2019
这个答案对初学者不是很有用,更不用说为了让这段代码正常工作,你必须在文件的开头添加语句。using Microsoft.VisualBasic.FileIO;
8赞 bytefish 1/13/2016 #8

我已经为 .NET 编写了 TinyCsvParser

它在 MIT 许可下发布:

可以使用 NuGet 进行安装。在包管理器控制台中运行以下命令。

PM> Install-Package TinyCsvParser

用法

想象一下,我们在 CSV 文件中列出了人员,其中包含他们的名字、姓氏和出生日期。persons.csv

FirstName;LastName;BirthDate
Philipp;Wagner;1986/05/12
Max;Musterman;2014/01/02

我们系统中相应的域模型可能如下所示。

private class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
}

使用 TinyCsvParser 时,必须定义 CSV 数据中的列与域模型中的属性之间的映射。

private class CsvPersonMapping : CsvMapping<Person>
{

    public CsvPersonMapping()
        : base()
    {
        MapProperty(0, x => x.FirstName);
        MapProperty(1, x => x.LastName);
        MapProperty(2, x => x.BirthDate);
    }
}

然后,我们可以使用映射来解析 CSV 数据。CsvParser

namespace TinyCsvParser.Test
{
    [TestFixture]
    public class TinyCsvParserTest
    {
        [Test]
        public void TinyCsvTest()
        {
            CsvParserOptions csvParserOptions = new CsvParserOptions(true, new[] { ';' });
            CsvPersonMapping csvMapper = new CsvPersonMapping();
            CsvParser<Person> csvParser = new CsvParser<Person>(csvParserOptions, csvMapper);

            var result = csvParser
                .ReadFromFile(@"persons.csv", Encoding.ASCII)
                .ToList();

            Assert.AreEqual(2, result.Count);

            Assert.IsTrue(result.All(x => x.IsValid));
            
            Assert.AreEqual("Philipp", result[0].Result.FirstName);
            Assert.AreEqual("Wagner", result[0].Result.LastName);

            Assert.AreEqual(1986, result[0].Result.BirthDate.Year);
            Assert.AreEqual(5, result[0].Result.BirthDate.Month);
            Assert.AreEqual(12, result[0].Result.BirthDate.Day);

            Assert.AreEqual("Max", result[1].Result.FirstName);
            Assert.AreEqual("Mustermann", result[1].Result.LastName);

            Assert.AreEqual(2014, result[1].Result.BirthDate.Year);
            Assert.AreEqual(1, result[1].Result.BirthDate.Month);
            Assert.AreEqual(1, result[1].Result.BirthDate.Day);
        }
    }
}

用户指南

完整的用户指南可在以下网址获得:

0赞 radsdau 7/26/2016 #9

基于 unlimit 的帖子 如何使用 C# split() 函数正确拆分 CSV?

string[] tokens = System.Text.RegularExpressions.Regex.Split(paramString, ",");

注意:这不处理转义/嵌套逗号等,因此仅适用于某些简单的 CSV 列表。

评论

2赞 EKS 9/20/2016
这是非常糟糕的,而且可能很慢:)
1赞 radsdau 9/30/2016
可能,但它完美地工作,并且简单地用于一小部分参数,因此是一个有效且有用的解决方案。为什么要投反对票?“非常糟糕”有点极端,你不觉得吗?
1赞 NStuke 10/11/2016
它不处理转义/嵌套逗号等。在某些情况下可以工作,但绝对不适用于所有 csv 文件
0赞 radsdau 10/11/2016
你是对的;我将编辑回复以反映这一点。谢谢。但它仍然有它的位置。
0赞 dubvfan87 2/12/2020
这非常适合我的用例,我正在构建 sql server clr dll,并且不能使用任何其他外部包。我只需要解析一个带有文件名和行数的简单 csv 文件。
12赞 Jonas_Hess 7/27/2016 #10

此解决方案使用官方 Microsoft.VisualBasic 程序集来分析 CSV。

优势:

  • 分隔符转义
  • 忽略标头
  • 修剪空间
  • 忽略评论

法典:

    using Microsoft.VisualBasic.FileIO;

    public static List<List<string>> ParseCSV (string csv)
    {
        List<List<string>> result = new List<List<string>>();


        // To use the TextFieldParser a reference to the Microsoft.VisualBasic assembly has to be added to the project. 
        using (TextFieldParser parser = new TextFieldParser(new StringReader(csv))) 
        {
            parser.CommentTokens = new string[] { "#" };
            parser.SetDelimiters(new string[] { ";" });
            parser.HasFieldsEnclosedInQuotes = true;

            // Skip over header line.
            //parser.ReadLine();

            while (!parser.EndOfData)
            {
                var values = new List<string>();

                var readFields = parser.ReadFields();
                if (readFields != null)
                    values.AddRange(readFields);
                result.Add(values);
            }
        }

        return result;
    }
1赞 Alex Begun 12/21/2016 #11

这是我的 KISS 实现......

using System;
using System.Collections.Generic;
using System.Text;

class CsvParser
{
    public static List<string> Parse(string line)
    {
        const char escapeChar = '"';
        const char splitChar = ',';
        bool inEscape = false;
        bool priorEscape = false;

        List<string> result = new List<string>();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < line.Length; i++)
        {
            char c = line[i];
            switch (c)
            {
                case escapeChar:
                    if (!inEscape)
                        inEscape = true;
                    else
                    {
                        if (!priorEscape)
                        {
                            if (i + 1 < line.Length && line[i + 1] == escapeChar)
                                priorEscape = true;
                            else
                                inEscape = false;
                        }
                        else
                        {
                            sb.Append(c);
                            priorEscape = false;
                        }
                    }
                    break;
                case splitChar:
                    if (inEscape) //if in escape
                        sb.Append(c);
                    else
                    {
                        result.Add(sb.ToString());
                        sb.Length = 0;
                    }
                    break;
                default:
                    sb.Append(c);
                    break;
            }
        }

        if (sb.Length > 0)
            result.Add(sb.ToString());

        return result;
    }

}

评论

1赞 John Leidegren 1/20/2018
这不处理在 CSV 文件中有效的带引号的字符串中的换行符。
0赞 David Yates 1/26/2018
亚历克斯,约翰想说的是,RFC 4180 (ietf.org/rfc/rfc4180.txt -- 参见第 2 节和第 6 项)允许一列在列中间有一个 CR LF,有效地将其分布在文件中的 2 行上。在大多数情况下,您的解决方案可能会运行良好(特别是如果 CSV 文件是通过保存 Excel 创建的),但它不包括此边缘情况。上面提到的 CsvHelper 应该考虑到这种情况。
0赞 Alex Begun 3/8/2018
是的,这是真的,但是如果您的 CSV 中有 CR LF,您可能不应该使用 CSV,而是更合适的格式,例如 json 或 xml 或固定长度格式。
1赞 Andrew_STOP_RU_WAR_IN_UA 4/1/2017 #12

前段时间,我编写了基于库的 CSV 读/写简单类。使用这个简单的类,您将能够像使用 2 维数组一样使用 CSV。您可以通过以下链接找到我的课程: https://github.com/ukushu/DataExporterMicrosoft.VisualBasic

使用方法的简单示例:

Csv csv = new Csv("\t");//delimiter symbol

csv.FileOpen("c:\\file1.csv");

var row1Cell6Value = csv.Rows[0][5];

csv.AddRow("asdf","asdffffff","5")

csv.FileSave("c:\\file2.csv");

对于读取标题,您只需要读取单元格:)csv.Rows[0]

1赞 polina-c 6/17/2017 #13

此代码将 csv 读取到 DataTable:

public static DataTable ReadCsv(string path)
{
    DataTable result = new DataTable("SomeData");
    using (TextFieldParser parser = new TextFieldParser(path))
    {
        parser.TextFieldType = FieldType.Delimited;
        parser.SetDelimiters(",");
        bool isFirstRow = true;
        //IList<string> headers = new List<string>();

        while (!parser.EndOfData)
        {
            string[] fields = parser.ReadFields();
            if (isFirstRow)
            {
                foreach (string field in fields)
                {
                    result.Columns.Add(new DataColumn(field, typeof(string)));
                }
                isFirstRow = false;
            }
            else
            {
                int i = 0;
                DataRow row = result.NewRow();
                foreach (string field in fields)
                {
                    row[i++] = field;
                }
                result.Rows.Add(row);
            }
        }
    }
    return result;
}

评论

2赞 user3285954 2/3/2018
TextFieldParser 位于 Microsoft.VisualBasic.dll 中。
1赞 Cinchoo 10/4/2017 #14

此列表中的另一个是 Cinchoo ETL - 一个用于读取和写入多种文件格式(CSV、平面文件、Xml、JSON 等)的开源库

下面的示例显示了如何快速读取 CSV 文件(无需 POCO 对象)

string csv = @"Id, Name
1, Carl
2, Tom
3, Mark";

using (var p = ChoCSVReader.LoadText(csv)
    .WithFirstLineHeader()
    )
{
    foreach (var rec in p)
    {
        Console.WriteLine($"Id: {rec.Id}");
        Console.WriteLine($"Name: {rec.Name}");
    }
}

下面的示例显示了如何使用 POCO 对象读取 CSV 文件

public partial class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; }
}

static void CSVTest()
{
    string csv = @"Id, Name
1, Carl
2, Tom
3, Mark";

    using (var p = ChoCSVReader<EmployeeRec>.LoadText(csv)
        .WithFirstLineHeader()
        )
    {
        foreach (var rec in p)
        {
            Console.WriteLine($"Id: {rec.Id}");
            Console.WriteLine($"Name: {rec.Name}");
        }
    }
}

请查看 CodeProject 上的文章,了解如何使用它。

1赞 John Leidegren 1/20/2018 #15

单一源文件解决方案,满足简单的解析需求,非常有用。处理所有令人讨厌的边缘情况。例如换行规范化和处理带引号的字符串文本中的新行。欢迎您的光临!

如果 CSV 文件有标题,则只需从第一行读出列名(和计算列索引)。就这么简单。

请注意,这是一个 LINQPad 方法,如果不使用 LINQPad,则可能需要删除该方法。Dump

void Main()
{
    var file1 = "a,b,c\r\nx,y,z";
    CSV.ParseText(file1).Dump();

    var file2 = "a,\"b\",c\r\nx,\"y,z\"";
    CSV.ParseText(file2).Dump();

    var file3 = "a,\"b\",c\r\nx,\"y\r\nz\"";
    CSV.ParseText(file3).Dump();

    var file4 = "\"\"\"\"";
    CSV.ParseText(file4).Dump();
}

static class CSV
{
    public struct Record
    {
        public readonly string[] Row;

        public string this[int index] => Row[index];

        public Record(string[] row)
        {
            Row = row;
        }
    }

    public static List<Record> ParseText(string text)
    {
        return Parse(new StringReader(text));
    }

    public static List<Record> ParseFile(string fn)
    {
        using (var reader = File.OpenText(fn))
        {
            return Parse(reader);
        }
    }

    public static List<Record> Parse(TextReader reader)
    {
        var data = new List<Record>();

        var col = new StringBuilder();
        var row = new List<string>();
        for (; ; )
        {
            var ln = reader.ReadLine();
            if (ln == null) break;
            if (Tokenize(ln, col, row))
            {
                data.Add(new Record(row.ToArray()));
                row.Clear();
            }
        }

        return data;
    }

    public static bool Tokenize(string s, StringBuilder col, List<string> row)
    {
        int i = 0;

        if (col.Length > 0)
        {
            col.AppendLine(); // continuation

            if (!TokenizeQuote(s, ref i, col, row))
            {
                return false;
            }
        }

        while (i < s.Length)
        {
            var ch = s[i];
            if (ch == ',')
            {
                row.Add(col.ToString().Trim());
                col.Length = 0;
                i++;
            }
            else if (ch == '"')
            {
                i++;
                if (!TokenizeQuote(s, ref i, col, row))
                {
                    return false;
                }
            }
            else
            {
                col.Append(ch);
                i++;
            }
        }

        if (col.Length > 0)
        {
            row.Add(col.ToString().Trim());
            col.Length = 0;
        }

        return true;
    }

    public static bool TokenizeQuote(string s, ref int i, StringBuilder col, List<string> row)
    {
        while (i < s.Length)
        {
            var ch = s[i];
            if (ch == '"')
            {
                // escape sequence
                if (i + 1 < s.Length && s[i + 1] == '"')
                {
                    col.Append('"');
                    i++;
                    i++;
                    continue;
                }
                i++;
                return true;
            }
            else
            {
                col.Append(ch);
                i++;
            }
        }
        return false;
    }
}
0赞 John 1/18/2020 #16

如果有人想要一个片段,他们可以进入他们的代码,而不必绑定库或下载包。这是我写的一个版本:

    public static string FormatCSV(List<string> parts)
    {
        string result = "";

        foreach (string s in parts)
        {
            if (result.Length > 0)
            {
                result += ",";

                if (s.Length == 0)
                    continue;
            }

            if (s.Length > 0)
            {
                result += "\"" + s.Replace("\"", "\"\"") + "\"";
            }
            else
            {
                // cannot output double quotes since its considered an escape for a quote
                result += ",";
            }
        }

        return result;
    }

    enum CSVMode
    {
        CLOSED = 0,
        OPENED_RAW = 1,
        OPENED_QUOTE = 2
    }

    public static List<string> ParseCSV(string input)
    {
        List<string> results;

        CSVMode mode;

        char[] letters;

        string content;


        mode = CSVMode.CLOSED;

        content = "";
        results = new List<string>();
        letters = input.ToCharArray();

        for (int i = 0; i < letters.Length; i++)
        {
            char letter = letters[i];
            char nextLetter = '\0';

            if (i < letters.Length - 1)
                nextLetter = letters[i + 1];

            // If its a quote character
            if (letter == '"')
            {
                // If that next letter is a quote
                if (nextLetter == '"' && mode == CSVMode.OPENED_QUOTE)
                {
                    // Then this quote is escaped and should be added to the content

                    content += letter;

                    // Skip the escape character
                    i++;
                    continue;
                }
                else
                {
                    // otherwise its not an escaped quote and is an opening or closing one
                    // Character is skipped

                    // If it was open, then close it
                    if (mode == CSVMode.OPENED_QUOTE)
                    {
                        results.Add(content);

                        // reset the content
                        content = "";

                        mode = CSVMode.CLOSED;

                        // If there is a next letter available
                        if (nextLetter != '\0')
                        {
                            // If it is a comma
                            if (nextLetter == ',')
                            {
                                i++;
                                continue;
                            }
                            else
                            {
                                throw new Exception("Expected comma. Found: " + nextLetter);
                            }
                        }
                    }
                    else if (mode == CSVMode.OPENED_RAW)
                    {
                        // If it was opened raw, then just add the quote 
                        content += letter;
                    }
                    else if (mode == CSVMode.CLOSED)
                    {
                        // Otherwise open it as a quote 

                        mode = CSVMode.OPENED_QUOTE;
                    }
                }
            }
            // If its a comma seperator
            else if (letter == ',')
            {
                // If in quote mode
                if (mode == CSVMode.OPENED_QUOTE)
                {
                    // Just read it
                    content += letter;
                }
                // If raw, then close the content
                else if (mode == CSVMode.OPENED_RAW)
                {
                    results.Add(content);

                    content = "";

                    mode = CSVMode.CLOSED;
                }
                // If it was closed, then open it raw
                else if (mode == CSVMode.CLOSED)
                {
                    mode = CSVMode.OPENED_RAW;

                    results.Add(content);

                    content = "";
                }
            }
            else
            {
                // If opened quote, just read it
                if (mode == CSVMode.OPENED_QUOTE)
                {
                    content += letter;
                }
                // If opened raw, then read it
                else if (mode == CSVMode.OPENED_RAW)
                {
                    content += letter;
                }
                // It closed, then open raw
                else if (mode == CSVMode.CLOSED)
                {
                    mode = CSVMode.OPENED_RAW;

                    content += letter;
                }
            }
        }

        // If it was still reading when the buffer finished
        if (mode != CSVMode.CLOSED)
        {
            results.Add(content);
        }

        return results;
    }
6赞 The Hawk 2/21/2020 #17

这是一个简短而简单的解决方案。

                using (TextFieldParser parser = new TextFieldParser(outputLocation))
                 {
                        parser.TextFieldType = FieldType.Delimited;
                        parser.SetDelimiters(",");
                        string[] headers = parser.ReadLine().Split(',');
                        foreach (string header in headers)
                        {
                            dataTable.Columns.Add(header);
                        }
                        while (!parser.EndOfData)
                        {
                            string[] fields = parser.ReadFields();
                            dataTable.Rows.Add(fields);
                        }
                    }
1赞 Carlos A Merighe - Utah 6/29/2022 #18

此解析器支持列中的嵌套逗号和引号:

static class CSVParser
{
    public static string[] ParseLine(string line)
    {
        List<string> cols = new List<string>();
        string value = null;

        for(int i = 0; i < line.Length; i++)
        {
            switch(line[i])
            {
                case ',':
                    cols.Add(value);
                    value = null;
                    if(i == line.Length - 1)
                    {// It ends with comma
                        cols.Add(null);
                    }
                    break;
                case '"':
                    cols.Add(ParseEnclosedColumn(line, ref i));
                    i++;
                    break;
                default:
                    value += line[i];
                    if (i == line.Length - 1)
                    {// Last character
                        cols.Add(value);                           
                    }
                    break;
            }
        }

        return cols.ToArray();
    }//ParseLine

    static string ParseEnclosedColumn(string line, ref int index)
    {// Example: "b"",bb"
        string value = null;
        int numberQuotes = 1;
        int index2 = index;

        for (int i = index + 1; i < line.Length; i++)
        {
            index2 = i;
            switch (line[i])
            {
                case '"':
                    numberQuotes++;
                    if (numberQuotes % 2 == 0)
                    {
                        if (i < line.Length - 1 && line[i + 1] == ',')
                        {
                            index = i;
                            return value;
                        }
                    }
                    else if (i > index + 1 && line[i - 1] == '"')
                    {
                        value += '"';
                    }
                    break;
                default:
                    value += line[i];
                    break;
            }
        }

        index = index2;
        return value;
    }//ParseEnclosedColumn 
}//class CSVParser
0赞 Ladislav 7/27/2022 #19

对于较小的输入 CSV 数据,LINQ 就足够了。 例如,对于以下 CSV 文件内容:

schema_name,description,utype
“IX_HE”,“高能数据”,“x” “III_spectro”,“光谱数据”,“d” “VI_misc”,“杂项”,“f” “vcds1”,“目录仅在CDS中可用”,“d” “J_other”,“其他期刊的出版物”,“b”



当我们将整个内容读入名为 data 的单个字符串中时,那么

using System;
using System.IO;
using System.Linq;

var data = File.ReadAllText(Path2CSV);

// helper split characters
var newline = Environment.NewLine.ToCharArray();
var comma = ",".ToCharArray();
var quote = "\"".ToCharArray();

// split input string data to lines
var lines = data.Split(newline);

// first line is header, take the header fields
foreach (var col in lines.First().Split(comma)) {
    // do something with "col"
}

// we skip the first line, all the rest are real data lines/fields
foreach (var line in lines.Skip(1)) {
    // first we split the data line by comma character
    // next we remove double qoutes from each splitted element using Trim()
    // finally we make an array
    var fields = line.Split(comma)
        .Select(_ => { _ = _.Trim(quote); return _; })
        .ToArray();
    // do something with the "fields" array
}

评论

1赞 Ladislav 7/28/2022
注意,我们也可以将文件内容读成字符串数组,表示逐行使用:var lines = File.ReadAllLines(Path2CSV);