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

另外,我也见过人们使用ODBC/OLE DB通过文本驱动程序读取CSV的实例,很多人因为它的“缺点”而不鼓励这样做。这些缺点是什么?

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


当前回答

这个列表中的另一个,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上查看如何使用它的文章。

其他回答

这个解析器支持在列中嵌套逗号和引号:

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

这段代码读取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;
}

我已经为。net编写了TinyCsvParser,它是目前最快的。net解析器之一,并且高度可配置,可以解析几乎任何CSV格式。

它在MIT许可下发布:

https://github.com/bytefish/TinyCsvParser

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

PM> Install-Package TinyCsvParser

使用

假设我们在CSV文件Persons . CSV中有一个person列表,包含他们的名字、姓氏和生日。

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);
    }
}

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

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);
        }
    }
}

用户指南

完整的用户指南可在以下网址查阅:

http://bytefish.github.io/TinyCsvParser/

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

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

有时候你并不拥有你想读的对象。在这种情况下,您可以使用流畅映射,因为您不能将属性放在类上。

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>();
    }
}

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

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

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