Разбор CSV-файлов в C #, с заголовком

266

Есть ли по умолчанию / официальный / рекомендуемый способ анализа файлов CSV в C #? Я не хочу катить свой собственный парсер.

Кроме того, я видел случаи, когда люди использовали ODBC / OLE DB для чтения CSV через драйвер текста, и многие люди препятствовали этому из-за его «недостатков». Каковы эти недостатки?

В идеале я ищу способ, с помощью которого я могу прочитать CSV по имени столбца, используя первую запись в качестве имени заголовка / поля. Некоторые из приведенных ответов являются правильными, но работают для десериализации файла в классы.

Дэвид Пфеффер
источник

Ответы:

138

Позвольте библиотеке обрабатывать все мелкие детали для вас! :-)

Проверьте FileHelpers и оставайтесь сухими - не повторяйте себя - не нужно заново изобретать колесо в миллионный раз ....

По сути, вам просто нужно определить эту форму ваших данных - полей в отдельной строке в CSV - с помощью открытого класса (и таких хорошо продуманных атрибутов, как значения по умолчанию, замены для значений NULL и т. Д.), Точки движок FileHelpers для файла, а для бинго - вы получаете все записи из этого файла. Одна простая операция - отличная производительность!

marc_s
источник
1
пока вам не понадобится что-то действительно настраиваемое (и, в любом случае, большая часть этого может быть реализована в виде расширений), FileHelpers - безусловно, лучший путь, действительно удобное, проверенное и хорошо
работающее
3
По состоянию на 1 июня 2015 года единственным способом загрузить FileHelpers был его поиск на sourceforge.net. Вот ссылка, используемая: sourceforge.net/projects/filehelpers/?source=directory
Судханшу Мишра
2
@dotnetguy Мы на пути к выпуску 3.1 (в настоящее время 3.1-rc2). Также мы изменили дизайн сайта: www.filehelpers.net, вы можете скачать последнюю версию оттуда
Маркос Мели
1
@MarcosMeli большое спасибо! Я уже использовал FileHelpers в одном из моих проектов, и использовать его было очень просто - слава команде. Я планирую блог на нем в ближайшее время и, кстати, - Люблю новый сайт - молодец!
Судханшу Мишра
FileHelpers не обрабатывает запятые в кавычках в CSV должным образом или фактически отображает заголовки полей, ожидая, что столбцы будут в том же порядке, что и поля, объявленные в вашем типе. Я бы не стал использовать это лично.
Аластер Мо
358

Парсер 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

PS Если вам нужен экспортер CSV , попробуйте CsvExport (диск: я один из авторов)

Alex
источник
2
Из моего опыта TextFieldParser не очень хорошо работает с большими (например,> 250 МБ) файлами. :(
MBoros
6
TextFieldParser реализует IDisposable, поэтому может быть лучше использовать его в предложении using. Хороший ответ иначе.
Крис Буш
3
В конструкторе вы можете использовать кодировку, отличную от кодировки по умолчанию, например: new TextFieldParser ("c: \ temp \ test.csv", System.Text.Encoding.UTF8)
neural5torm
1
Обратите внимание, что если какое-либо поле в вашем CSV содержит пустые строки, они будут пропущены TextFieldParser.ReadLine(). См. Документацию TextFieldParser
McNux,
3
Есть ли способ получить это в .NET Core?
Хьюго Цинк
183

CsvHelper (библиотека, которую я поддерживаю) будет читать файл CSV в пользовательских объектах.

var csv = new CsvReader( File.OpenText( "file.csv" ) );
var myCustomObjects = csv.GetRecords<MyCustomObject>();

Иногда вам не принадлежат объекты, которые вы пытаетесь прочитать. В этом случае вы можете использовать свободное отображение, потому что вы не можете поместить атрибуты в класс.

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

РЕДАКТИРОВАТЬ:

CsvReader теперь требует, чтобы CultureInfo передавался в конструктор ( https://github.com/JoshClose/CsvHelper/issues/1441 ).

Пример:

var csv = new CsvReader(File.OpenText("file.csv"), System.Globalization.CultureInfo.CurrentCulture);
Джош Клоуз
источник
18
Я согласен с @ kubal5003. Что меня продало, так это то, что он был у вас в наличии в виде пакета NuGet. Спасибо человек, это быстро, и делает все чтение CSV мне нужно.
Громер
7
Это чертовски быстро. 1,3 миллиона записей прочитаны и десериализованы за 10 секунд.
marisks
2
Отличная библиотека очень проста в реализации. Я бы просто предложил Джошу обновить его ответ здесь, потому что библиотека немного изменилась с тех пор, как этот ответ был написан, и вы больше не можете создавать экземпляр CsvHelper (теперь это только пространство имен), но вы должны использовать класс CsvReader.
Марко
1
CsvClassMap, кажется, не существует в последней версии CsvHelper?
knocte
1
knocte, теперь он называется ClassMap. Есть и другие изменения, такие как необходимость чтения перед запросом записи заголовка (которая, кстати, устанавливается на то, что было прочитано при первом вызове Read ()). Как уже упоминалось ранее, с ним очень быстро и легко работать.
Норги
31

В бизнес-приложении я использую проект с открытым исходным кодом на 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();
    }
}

Как видите, с ним очень легко работать.

AlexN
источник
20

Я знаю, что немного поздно, но только что нашел библиотеку, в Microsoft.VisualBasic.FileIOкоторой есть TextFieldParserкласс для обработки CSV-файлов.

user1131926
источник
1
Пример использования этого API; msdn.microsoft.com/en-us/library/cakac7e6(v=vs.90).aspx
AnneTheAgile
12

Если вам нужно только чтение CSV-файлов, я рекомендую эту библиотеку: A Fast CSV Reader.
Если вам также нужно генерировать CSV-файлы, используйте эту: FileHelpers

Оба из них являются бесплатными и с открытым исходным кодом.

Giorgi
источник
FileHelpers имеет привлекательную сводку: filehelpers.com FileHelpers - это бесплатная и простая в использовании библиотека .NET для импорта / экспорта данных фиксированной длины или записей с разделителями в файлах, строках или потоках.
AnneTheAgile
Хотя эта ссылка может ответить на вопрос, ответы только на ссылки не рекомендуются при переполнении стека, вы можете улучшить этот ответ, взяв жизненно важные части ссылки и вставив его в свой ответ, это гарантирует, что ваш ответ все еще остается ответом, если ссылка будет изменена или удалено :)
WhatsThePoint
11

Вот вспомогательный класс, который я часто использую, на случай, если кто-нибудь когда-нибудь вернется в этот поток (я хотел поделиться им).

Я использую это для простоты переноса на проекты, готовые к использованию:

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 helper: исправлена ​​ошибка, при которой последний символ новой строки создавал новую строку]

Base33
источник
17
если какая-либо из записей csv содержит запятую (,), этот код не будет работать.
Хакан
Чтобы облегчить задачу, я использовал символ трубы в качестве разделителя. '|'
Base33
отличное решение. Просто вопрос по поводу второго фрагмента. Какой тип объекта Персона
Какао Дев
@CocoaDev Это класс, который содержит два строковых свойства - Name и TelephoneNo. Чисто для примера, хотя. Если какое-либо из свойств было целым числом, это должно быть просто прямое преобразование (с проверкой?).
Base33
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;
    }
Jonas_Hess
источник
7

Я написал TinyCsvParser для .NET, который является одним из самых быстрых анализаторов .NET и легко настраивается для анализа почти любого формата CSV.

Выпущено по лицензии 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);
        }
    }
}

Гид пользователя

Полное руководство пользователя доступно по адресу:

bytefish
источник
1

Вот моя реализация 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
Это не касается разрывов строк внутри строк в кавычках, что допустимо в CSV-файле.
Джон Лейдгрен
Алекс, Джон пытается сказать, что RFC 4180 ( ietf.org/rfc/rfc4180.txt - см. Раздел 2 и пункт 6) позволяет столбцу иметь CR LF в середине столбца, эффективно распространяя его на 2 строки в файле. Ваше решение, вероятно, будет работать хорошо в большинстве случаев (особенно, если файлы CSV были созданы путем сохранения из Excel), но оно не охватывает этот крайний случай. CsvHelper, упомянутый выше, должен принять это во внимание.
Дэвид Йейтс
Да, это правда, но если у вас есть CR LF в CSV, скорее всего, вам следует использовать не CSV, а что-то более подходящее, например, json или xml или формат фиксированной длины.
Алекс Бегун
1

Некоторое время назад я написал простой класс для чтения / записи CSV на основе Microsoft.VisualBasicбиблиотеки. Используя этот простой класс, вы сможете работать с CSV, как с двухмерным массивом. Вы можете найти мой класс по следующей ссылке: https://github.com/ukushu/DataExporter

Простой пример использования:

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

Решение с одним исходным файлом для простого анализа, полезно. Имеет дело со всеми неприятными крайними случаями. Например, нормализация новой строки и обработка новых строк в строковых литералах в кавычках. Пожалуйста!

Если у вашего CSV-файла есть заголовок, вы просто считываете имена столбцов (и вычисляете индексы столбцов) из первой строки. Просто как тот.

Обратите внимание, что Dumpэто метод LINQPad, вы можете удалить его, если вы не используете LINQPad.

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;
    }
}
Джон Лейдегрен
источник
1

Еще одна в этом списке, 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}");
    }
}

Пример ниже показывает, как прочитать файл CSV, используя объект POCO

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 о том, как его использовать.

Райн
источник
0

Основано на посте unlimit о том, как правильно разделить CSV с помощью функции C # split ()? :

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

ПРИМЕЧАНИЕ: это не обрабатывает экранированные / вложенные запятые и т. Д., И, следовательно, подходит только для некоторых простых списков CSV.

radsdau
источник
2
Это очень плохо и, вероятно, медленно :)
EKS
1
Вероятно, но это работает отлично и просто для небольшого набора параметров, поэтому является допустимым и полезным решением. Зачем понижать это? «Очень плохо» - это немного экстрим, тебе не кажется?
Радсдау
1
Он не обрабатывает экранированные / вложенные запятые и т. Д. Будет работать в некоторых случаях, но определенно не будет работать для всех CSV-файлов
NStuke
Вы правы; Я отредактирую ответ, чтобы отразить это. Спасибо. Но у него все еще есть свое место.
Rdsdau
Это отлично работает для моего случая использования, когда я собираю SQL Server CLR DLL и не могу использовать ни один из этих внешних пакетов. Мне просто нужно было проанализировать простой CSV-файл с именем файла и количеством строк.
dubvfan87
0

Этот код читает 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;
}
полина-с
источник
1
TextFieldParser находится в Microsoft.VisualBasic.dll.
user3285954
0

Если кому-то нужен фрагмент кода, он может добавить свой код без необходимости привязывать библиотеку или загружать пакет. Вот версия, которую я написал:

    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;
    }
Джон
источник
0

Вот краткое и простое решение.

                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);
                        }
                    }
Ястреб
источник