【问题标题】:Reading CSV files in C# [closed]在 C# 中读取 CSV 文件 [关闭]
【发布时间】:2009-10-09 16:10:47
【问题描述】:

有谁知道允许您在 C# 中解析和读取.csv 文件的开源库?

【问题讨论】:

标签: c# open-source csv


【解决方案1】:

在这里,由您真正使用泛型集合和迭代器块编写。它使用双转义约定支持双引号括起来的文本字段(包括跨越多行的文本字段)(因此带引号的字段内的"" 读取为单引号字符)。不支持:

  • 单引号括起来的文本
  • \ -转义的引用文本
  • 备用分隔符(尚不适用于竖线或制表符分隔的字段)
  • 以引号开头的不带引号的文本字段

但如果您需要,所有这些都可以轻松添加。我没有在任何地方对其进行基准测试(我很想看到一些结果),但性能应该非常好 - 无论如何都比基于.Split() 的任何东西都要好。

Now on GitHub

更新:感觉像是添加了单引号括起来的文本支持。这是一个简单的更改,但我直接在回复窗口中输入了它,因此未经测试。如果您更喜欢旧的(经过测试的)代码,请使用底部的修订链接。

public static class CSV
{
    public static IEnumerable<IList<string>> FromFile(string fileName, bool ignoreFirstLine = false)
    {
        using (StreamReader rdr = new StreamReader(fileName))
        {
            foreach(IList<string> item in FromReader(rdr, ignoreFirstLine)) yield return item;
        }
    }

    public static IEnumerable<IList<string>> FromStream(Stream csv, bool ignoreFirstLine=false)
    {
        using (var rdr = new StreamReader(csv))
        {
            foreach (IList<string> item in FromReader(rdr, ignoreFirstLine)) yield return item;
        }
    }

    public static IEnumerable<IList<string>> FromReader(TextReader csv, bool ignoreFirstLine=false)
    {
        if (ignoreFirstLine) csv.ReadLine();

        IList<string> result = new List<string>();

        StringBuilder curValue = new StringBuilder();
        char c;
        c = (char)csv.Read();
        while (csv.Peek() != -1)
        {
            switch (c)
            {
                case ',': //empty field
                    result.Add("");
                    c = (char)csv.Read();
                    break;
                case '"': //qualified text
                case '\'':
                    char q = c;
                    c = (char)csv.Read();
                    bool inQuotes = true;
                    while (inQuotes && csv.Peek() != -1)
                    {
                        if (c == q)
                        {
                            c = (char)csv.Read();
                            if (c != q)
                                inQuotes = false;
                        }

                        if (inQuotes)
                        {
                            curValue.Append(c);
                            c = (char)csv.Read();
                        } 
                    }
                    result.Add(curValue.ToString());
                    curValue = new StringBuilder();
                    if (c == ',') c = (char)csv.Read(); // either ',', newline, or endofstream
                    break;
                case '\n': //end of the record
                case '\r':
                    //potential bug here depending on what your line breaks look like
                    if (result.Count > 0) // don't return empty records
                    {
                        yield return result;
                        result = new List<string>();
                    }
                    c = (char)csv.Read();
                    break;
                default: //normal unqualified text
                    while (c != ',' && c != '\r' && c != '\n' && csv.Peek() != -1)
                    {
                        curValue.Append(c);
                        c = (char)csv.Read();
                    }
                    result.Add(curValue.ToString());
                    curValue = new StringBuilder();
                    if (c == ',') c = (char)csv.Read(); //either ',', newline, or endofstream
                    break;
            }
            
        }
        if (curValue.Length > 0) //potential bug: I don't want to skip on a empty column in the last record if a caller really expects it to be there
            result.Add(curValue.ToString());
        if (result.Count > 0) 
            yield return result;

    }
}

【讨论】:

  • 它能处理引号内的逗号吗? "like,this" ... 它可以处理带引号的字符串中的回车吗? ...这些是一些容易引起麻烦的事情...
  • 是的,两者都可以。这就是引用字符串的全部意义所在。
  • 我仍然喜欢这个,但如果我不得不重来,我可能会继承 TextReader
  • 我正在做一些游戏开发,可能在早期阶段使用 .CSV 文件。我对你在这里拥有的东西以及继承 TextReader 感兴趣。
【解决方案2】:

看看 CodeProject 上的A Fast CSV Reader

【讨论】:

  • 是的,那个很棒,又名 LumenWorks.Framework.IO.Csv by Sebastien Lorien
【解决方案3】:

last time this question was asked,这是我给的the answer

如果您只是想用 C# 读取 CSV 文件,最简单的方法是使用 Microsoft.VisualBasic.FileIO.TextFieldParser 类。它实际上内置在 .NET Framework 中,而不是第三方扩展。

是的,它在 Microsoft.VisualBasic.dll 中,但这并不意味着您不能在 C#(或任何其他 CLR 语言)中使用它。

这是一个用法示例,取自MSDN documentation

Using MyReader As New _
Microsoft.VisualBasic.FileIO.TextFieldParser("C:\testfile.txt")
   MyReader.TextFieldType = FileIO.FieldType.Delimited
   MyReader.SetDelimiters(",")
   Dim currentRow As String()
   While Not MyReader.EndOfData
      Try
         currentRow = MyReader.ReadFields()
         Dim currentField As String
         For Each currentField In currentRow
            MsgBox(currentField)
         Next
      Catch ex As Microsoft.VisualBasic.FileIO.MalformedLineException
      MsgBox("Line " & ex.Message & _
      "is not valid and will be skipped.")
      End Try
   End While
End Using

同样,这个示例是在 VB.NET 中,但将其转换为 C# 将是微不足道的。

【讨论】:

  • +1 我不知道这门课,但效果很好。
  • 我已经多次使用过这个课程,并且会推荐它。内置到 .NET 中意味着您不必担心许可/分发问题。
【解决方案4】:

我真的很喜欢FileHelpers 库。它速度很快,它是 100% 的 C#,它可以 免费 获得,它非常灵活且易于使用。

【讨论】:

  • FileHelpers 向导在快速创建标准类方面看起来非常有用。
【解决方案5】:

我正在用 C# 实现 Daniel Pryden 的答案,因此更容易剪切、粘贴和自定义。我认为这是解析 CSV 文件的最简单方法。只需添加一个引用,您就基本完成了。

Microsoft.VisualBasic 引用添加到您的项目

然后这里是乔尔的回答中的 C# 示例代码:

using (Microsoft.VisualBasic.FileIO.TextFieldParser MyReader = new           
       Microsoft.VisualBasic.FileIO.TextFieldParser(filename))
{
    MyReader.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited;
    MyReader.SetDelimiters(",");

    while (!MyReader.EndOfData)
    {
        try
        {
            string[] fields = MyReader.ReadFields();
            if (first) 
            {
                first = false;
                continue;
            }

            // This is how I treat my data, you'll need to throw this out.

            //"Type"    "Post Date" "Description"   "Amount"
            LineItem li = new LineItem();

            li.date        = DateTime.Parse(fields[1]);
            li.description = fields[2];
            li.Value       = Convert.ToDecimal(fields[3]);

            lineitems1.Add(li);
        }
        catch (Microsoft.VisualBasic.FileIO.MalformedLineException ex)
        {
            MessageBox.Show("Line " + ex.Message + 
                            " is not valid and will be skipped.");
        }
    }
}

【讨论】:

    【解决方案6】:

    除了解析/读取之外,一些库还会为您做其他好事,例如将解析的数据转换为对象。

    这是使用CsvHelper(我维护的一个库)将 CSV 文件读入对象的示例。

    var csv = new CsvHelper( File.OpenRead( "file.csv" ) );
    var myCustomObjectList = csv.Reader.GetRecords<MyCustomObject>();
    

    默认情况下,约定用于将标题/列与属性匹配。您可以通过更改设置来更改行为。

    // Using attributes:
    public class MyCustomObject
    {
        [CsvField( Name = "First Name" )]
        public string StringProperty { get; set; }
    
        [CsvField( Index = 0 )]
        public int IntProperty { get; set; }
    
        [CsvField( Ignore = true )]
        public string ShouldIgnore { get; set; }
    }
    

    有时您并不“拥有”您想要用来填充数据的对象。在这种情况下,您可以使用流畅的类映射。

    // Fluent class mapping:
    public sealed class MyCustomObjectMap : CsvClassMap<MyCustomObject>
    {
        public MyCustomObjectMap()
        {
            Map( m => m.StringProperty ).Name( "First Name" );
            Map( m => m.IntProperty ).Index( 0 );
            Map( m => m.ShouldIgnore ).Ignore();
        }
    }
    

    【讨论】:

    • 我从文档中不明白的是,我什么时候使用映射,什么时候不应该使用映射?比方说,我只想解析一个 xml 文件和额外的关键字及其附加数据。我需要为此使用地图吗?
    • 如果您正在解析 XML,则不应使用 CSV 库来执行此操作。自 2.0 以来不再存在属性映射。您使用映射文件的原因是如果您想更改库读取文件的任何默认方式。映射将使您能够对文件的读取/写入方式进行大量控制。
    • 哎呀,那是口误......我的意思是*CSV当然。 .NET 4.0/5 中不存在映射?另外,关于这一点,您建议我使用什么来解析 CSV 并仅获取某些关键字及其数据?
    • 这个网站会有太多的讨论。可以在github或者google群里提问吗? github.com/JoshClose/CsvHelper/issuesgroups.google.com/forum/#!forum/csvhelper
    【解决方案7】:

    您可以使用 Microsoft.VisualBasic.FileIO.TextFieldParser

    从上面的文章中获取下面的代码示例

    static void Main()
            {
                string csv_file_path=@"C:\Users\Administrator\Desktop\test.csv";
    
                DataTable csvData = GetDataTabletFromCSVFile(csv_file_path);
    
                Console.WriteLine("Rows count:" + csvData.Rows.Count);
    
                Console.ReadLine();
            }
    
    
    private static DataTable GetDataTabletFromCSVFile(string csv_file_path)
            {
                DataTable csvData = new DataTable();
    
                try
                {
    
                using(TextFieldParser csvReader = new TextFieldParser(csv_file_path))
                    {
                        csvReader.SetDelimiters(new string[] { "," });
                        csvReader.HasFieldsEnclosedInQuotes = true;
                        string[] colFields = csvReader.ReadFields();
                        foreach (string column in colFields)
                        {
                            DataColumn datecolumn = new DataColumn(column);
                            datecolumn.AllowDBNull = true;
                            csvData.Columns.Add(datecolumn);
                        }
    
                        while (!csvReader.EndOfData)
                        {
                            string[] fieldData = csvReader.ReadFields();
                            //Making empty value as null
                            for (int i = 0; i < fieldData.Length; i++)
                            {
                                if (fieldData[i] == "")
                                {
                                    fieldData[i] = null;
                                }
                            }
                            csvData.Rows.Add(fieldData);
                        }
                    }
                }
                catch (Exception ex)
                {
                }
                return csvData;
            }
    

    【讨论】: