Analiza plików CSV w C #, z nagłówkiem

266

Czy istnieje domyślny / oficjalny / zalecany sposób analizowania plików CSV w języku C #? Nie chcę zrolować własnego parsera.

Widziałem również przypadki osób korzystających z ODBC / OLE DB do czytania CSV za pomocą sterownika Text i wiele osób zniechęca to ze względu na „wady”. Jakie są te wady?

Idealnie szukam sposobu, w jaki mogę odczytać CSV według nazwy kolumny, używając pierwszego rekordu jako nazwy nagłówka / pola. Niektóre z podanych odpowiedzi są poprawne, ale działają w zasadzie w celu deserializacji pliku na klasy.

David Pfeffer
źródło

Odpowiedzi:

138

Pozwól bibliotece zająć się wszystkimi drobiazgowymi szczegółami! :-)

Sprawdź FileHelpers i pozostań SUCHY - nie powtarzaj się - nie trzeba od nowa wymyślać koła po raz gazillionowy ....

Zasadniczo wystarczy zdefiniować ten kształt danych - pola w indywidualnej linii w CSV - za pomocą klasy publicznej (i tak dobrze przemyślanych atrybutów, takich jak wartości domyślne, zamienniki wartości NULL itd.), Punkt silnik FileHelpers w pliku i bingo - odzyskujesz wszystkie wpisy z tego pliku. Jedna prosta operacja - doskonała wydajność!

marc_s
źródło
1
dopóki nie będziesz potrzebować czegoś naprawdę niestandardowego (a większość z nich i tak może zostać zaimplementowana jako rozszerzenie) FileHelpers to zdecydowanie najlepsza droga, naprawdę wygodne, przetestowane i dobrze działające rozwiązanie
mikus
3
Od 1 czerwca 2015 r. Jedynym sposobem, w jaki mogłem pobrać FileHelpers, było wyszukiwanie go na sourceforge.net. Oto użyty link: sourceforge.net/projects/filehelpers/?source=directory
Sudhanshu Mishra
2
@dotnetguy jesteśmy w drodze do wydania wersji 3.1 (obecnie 3.1-RC2) jest obecnie dostępna. Również przeprojektowaliśmy stronę: www.filehelpers.net, z której możesz pobrać najnowszą wersję
Marcos Meli
1
@MarcosMeli wielkie dzięki! Korzystałem już z FileHelpers w jednym z moich projektów i korzystanie z niego było dziecinnie proste - podziękowania dla zespołu. Planuję wkrótce na nim blog i btw - Uwielbiam nową stronę - dobra robota!
Sudhanshu Mishra
FileHelpers nie obsługuje poprawnych cudzysłowów w plikach CSV ani nie odwzorowuje nagłówków pól, oczekując zamiast tego, że kolumny są w tej samej kolejności, w jakiej pola są zadeklarowane w twoim typie. Nie użyłbym tego osobiście.
Alastair Maw
357

Parser CSV jest teraz częścią .NET Framework.

Dodaj odwołanie do Microsoft.VisualBasic.dll (działa dobrze w C #, nieważne nazwy)

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

Dokumenty są tutaj - klasa TextFieldParser

PS Jeśli potrzebujesz eksportera CSV , wypróbuj CsvExport (ujawnij: jestem jednym z autorów)

Alex
źródło
2
Z mojego doświadczenia wynika, że ​​TextFieldParser nie działa dobrze z dużymi plikami (np.> 250 Mb). :(
MBoros
6
TextFieldParser implementuje IDisposable, więc najlepiej użyć go w klauzuli using. W przeciwnym razie dobra odpowiedź.
Chris Bush,
3
W konstruktorze możesz chcieć użyć innego kodowania niż domyślnie, na przykład: new TextFieldParser („c: \ temp \ test.csv”, System.Text.Encoding.UTF8)
neural5torm
1
Pamiętaj, że jeśli dowolne pole w pliku CSV zawiera puste linie, zostaną one pominięte TextFieldParser.ReadLine(). Zobacz dokumentację TextFieldParser
mcNux
3
Czy istnieje sposób, aby uzyskać to w .NET Core?
Hugo Zink
183

CsvHelper (biblioteka, którą prowadzę) wczyta plik CSV do niestandardowych obiektów.

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

Czasami nie jesteś właścicielem obiektów, na które próbujesz odczytać. W takim przypadku możesz użyć płynnego mapowania, ponieważ nie możesz przypisać atrybutów do klasy.

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

EDYTOWAĆ:

CsvReader wymaga teraz przekazania CultureInfo do konstruktora ( https://github.com/JoshClose/CsvHelper/issues/1441 ).

Przykład:

var csv = new CsvReader(File.OpenText("file.csv"), System.Globalization.CultureInfo.CurrentCulture);
Josh Close
źródło
18
Zgadzam się z @ kubal5003. To, co mnie sprzedało, to to, że masz go w pakiecie NuGet. Dzięki stary, jest szybki i wykonuje wszystkie potrzebne csv.
Gromer
7
Cholernie szybko. 1,3 miliona rekordów odczytanych i przekształconych z postaci szeregowej w 10 sekund
marisks
2
Świetna biblioteka bardzo łatwa do wdrożenia. Chciałbym tylko zasugerować Joshowi, aby zaktualizował swoją odpowiedź tutaj, ponieważ biblioteka zmieniła się nieco od czasu napisania tej odpowiedzi i nie można już tworzyć instancji CsvHelper (teraz jest to tylko przestrzeń nazw), ale musisz użyć klasy CsvReader.
Marko,
1
Wydaje się, że CsvClassMap nie istnieje w ostatniej wersji CsvHelper?
knocte,
1
knocte, teraz nazywa się ClassMap. Istnieją również inne zmiany, takie jak konieczność odczytu przed poproszeniem o rekord nagłówka (który, nawiasem mówiąc, ustawia się na wszystko, co odczytano przy pierwszym wywołaniu Read ()). Jak inni wspominali wcześniej, jest superszybki i łatwy w obsłudze.
norgie,
31

W aplikacji biznesowej korzystam z projektu Open Source na codeproject.com, CSVReader .

Działa dobrze i ma dobrą wydajność. Podany link zawiera pewne testy porównawcze.

Prosty przykład skopiowany ze strony projektu:

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

Jak widać, bardzo łatwo jest z nim pracować.

alexn
źródło
12

Jeśli potrzebujesz tylko czytać pliki csv, polecam tę bibliotekę: Szybki czytnik CSV
Jeśli potrzebujesz również wygenerować pliki csv, użyj tego: FileHelpers

Oba są bezpłatne i open source.

Giorgi
źródło
FileHelpers ma atrakcyjne podsumowanie: filehelpers.com FileHelpers to darmowa i łatwa w użyciu biblioteka .NET do importowania / eksportowania danych ze stałej długości lub ograniczonych rekordów w plikach, ciągach lub strumieniach.
AnneTheAgile,
Chociaż ten link może odpowiedzieć na pytanie, w przypadku przepełnienia stosu odradza się tylko odpowiedzi na link, możesz poprawić tę odpowiedź, biorąc istotne części linku i umieszczając go w swojej odpowiedzi, dzięki czemu Twoja odpowiedź będzie nadal odpowiedzią, jeśli link zostanie zmieniony lub usunięte :)
WhatsThePoint
11

Oto klasa pomocnicza, której często używam, na wypadek, gdyby ktoś wrócił do tego wątku (chciałem się nim podzielić).

Używam tego dla uproszczenia przenoszenia go do projektów gotowych do użycia:

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

I używaj go w następujący sposób:

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

[Zaktualizowano pomocnika csv: naprawiono błąd, w którym ostatni znak nowej linii utworzył nową linię]

Baza33
źródło
17
jeśli którykolwiek z wpisów csv zawiera przecinek (,), ten kod nie będzie działać.
hakan
Aby zachować lekkość, jako separator użyłem znaku fajki. „|”
Base33
doskonałe rozwiązanie. Pytanie tylko o drugim fragmencie. Jakim typem obiektu jest Osoba
Cocoa Dev
@CocoaDev Jest to klasa zawierająca dwie właściwości ciągów - Name i TelephoneNo. Ale tylko dla przykładu. Jeśli którakolwiek z właściwości była liczbą całkowitą, powinna to być zwykła konwersja (z czekiem?).
Base33
10

To rozwiązanie używa oficjalnego zestawu Microsoft.VisualBasic do analizowania CSV.

Zalety:

  • ucieczka ogranicznika
  • ignoruje nagłówek
  • przycinaj przestrzenie
  • ignoruj ​​komentarze

Kod:

    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
źródło
7

Napisałem TinyCsvParser dla platformy .NET, który jest jednym z najszybszych parserów .NET i jest wysoce konfigurowalny do analizowania prawie dowolnego formatu CSV.

Jest wydany na licencji MIT:

Możesz użyć NuGet, aby go zainstalować. Uruchom następujące polecenie w konsoli Menedżera pakietów .

PM> Install-Package TinyCsvParser

Stosowanie

Wyobraź sobie, że mamy listę Osób w pliku CSV persons.csvz ich imieniem, nazwiskiem i datą urodzenia.

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

Odpowiedni model domeny w naszym systemie może wyglądać tak.

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

Podczas korzystania z TinyCsvParser musisz zdefiniować mapowanie między kolumnami w danych CSV a właściwością w twoim modelu domeny.

private class CsvPersonMapping : CsvMapping<Person>
{

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

Następnie możemy użyć odwzorowania, aby przeanalizować dane CSV za pomocą 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);
        }
    }
}

Podręcznik użytkownika

Pełny Podręcznik użytkownika jest dostępny pod adresem:

bytefish
źródło
1

Oto moja implementacja 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;
    }

}
Alex Begun
źródło
1
Nie dotyczy to podziałów wierszy w ciągach cytowanych, które są poprawne w pliku CSV.
John Leidegren,
Alex, John próbuje powiedzieć, że RFC 4180 ( ietf.org/rfc/rfc4180.txt - patrz sekcja 2 i pozycja 6) pozwala kolumnie mieć CR LF na środku kolumny, skutecznie rozkładając ją na 2 linie w pliku. Twoje rozwiązanie prawdopodobnie będzie działać dobrze w większości przypadków (szczególnie jeśli pliki CSV zostały utworzone przez zapisanie poza programem Excel), ale nie obejmuje tego przypadku krawędzi. Wspomniany powyżej CsvHelper ma wziąć pod uwagę tę sprawę.
David Yates
Tak, to prawda, ale jeśli masz CR LF w CSV, prawdopodobnie nie powinieneś używać CSV, ale coś bardziej odpowiedniego, jak json, xml lub format o stałej długości.
Alex Begun
1

Jakiś czas temu napisałem prostą klasę do odczytu / zapisu CSV opartą na Microsoft.VisualBasicbibliotece. Korzystając z tej prostej klasy, będziesz mógł pracować z CSV jak z tablicą 2 wymiarów. Możesz znaleźć moją klasę, klikając następujący link: https://github.com/ukushu/DataExporter

Prosty przykład użycia:

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

Do odczytu nagłówka wystarczy odczyt csv.Rows[0]komórek :)

Andrzej
źródło
1

Przydatne rozwiązanie z jednym plikiem źródłowym do prostych analiz. Zajmuje się wszystkimi nieprzyjemnymi przypadkami na krawędzi. Takie jak normalizacja nowej linii i obsługa nowych linii w cytowanych literałach łańcuchowych. Proszę bardzo!

Jeśli plik CSV ma nagłówek, po prostu odczytujesz nazwy kolumn (i obliczasz indeksy kolumn) z pierwszego wiersza. Proste.

Pamiętaj, że Dumpjest to metoda LINQPad, możesz ją usunąć, jeśli nie używasz 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;
    }
}
John Leidegren
źródło
1

Kolejny na tej liście, Cinchoo ETL - biblioteka open source do odczytu i zapisu wielu formatów plików (CSV, płaski plik, Xml, JSON itp.)

Przykład poniżej pokazuje, jak szybko odczytać plik CSV (nie wymaga obiektu 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}");
    }
}

Przykład poniżej pokazuje, jak odczytać plik CSV za pomocą obiektu 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}");
        }
    }
}

Sprawdź artykuły w CodeProject, jak z niego korzystać.

RajN
źródło
0

Na podstawie postu unlimit dotyczącego prawidłowego podziału pliku CSV za pomocą funkcji C # split ()? :

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

UWAGA: nie obsługuje przecinków / zagnieżdżonych przecinków itp., Dlatego jest odpowiedni tylko dla niektórych prostych list CSV.

radsdau
źródło
2
To jest bardzo złe i prawdopodobnie wolne :)
EKS
1
Prawdopodobnie, ale działa idealnie i po prostu dla małego zestawu parametrów, dlatego jest ważnym i pomocnym rozwiązaniem. Po co głosować? „Very Bad” jest trochę ekstremalne, nie sądzisz?
radsdau 30.09.16
1
Nie obsługuje przecinków / zagnieżdżonych przecinków itp. Będzie działać w niektórych przypadkach, ale na pewno nie będzie działać dla wszystkich plików csv
NStuke 10.10.16
Masz rację; Przeredaguję odpowiedź, aby to odzwierciedlić. Dzięki. Ale nadal ma swoje miejsce.
radsdau,
Działa to idealnie w moim przypadku użycia, w którym buduję bibliotekę DLL serwera CLR i nie mogę użyć żadnego z tych zewnętrznych pakietów. Musiałem tylko przeanalizować prosty plik csv z nazwą pliku i liczbą wierszy.
dubvfan87
0

Ten kod czyta csv do 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;
}
polina-c
źródło
1
TextFieldParser znajduje się w Microsoft.VisualBasic.dll.
user3285954
0

Jeśli ktoś chce fragmentu kodu, może wejść do swojego kodu bez konieczności wiązania biblioteki lub pobierania pakietu. Oto wersja, którą napisałem:

    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;
    }
Jan
źródło
0

Oto krótkie i proste rozwiązanie.

                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);
                        }
                    }
Jastrząb
źródło