HOWTO · Csharp

C# Leia um arquivo CSV e armazene seus valores em uma array

Este artigo de como fazer mostra diferentes métodos para ler um arquivo CSV e armazenar seus valores em um array em C#. Ele introduz métodos como OpenRead(), ReadLine() e TextFieldParser

Nesta página

Um arquivo CSV é um arquivo separado por vírgulas, que é utilizado para armazenar dados de forma organizada. Ele normalmente armazena os dados em forma de tabela. A maioria das organizações empresariais armazena seus dados em arquivos CSV.

Em C#, podemos realizar várias operações em um arquivo CSV. Podemos ler, gravar e alterar um arquivo CSV. Este artigo concentra-se em ler um arquivo CSV e armazenar seus valores em um array: ler linhas uma a uma com StreamReader, carregar todas as linhas de uma vez com File.ReadAllLines() e analisar campos com TextFieldParser.

Os três primeiros exemplos usam o mesmo arquivo de exemplo Data.csv:

Name,Age
Alice,30
Bob,25

Ler um arquivo CSV com StreamReader

A classe StreamReader lê um arquivo como um fluxo de texto. File.OpenRead() abre o arquivo e ReadLine() retorna uma linha de cada vez até que EndOfStream seja atingido. Dividir cada linha na vírgula fornece os valores individuais, que o exemplo armazena em uma lista por coluna.

A sintaxe correta para usar os métodos OpenRead() e ReadLine() é a seguinte:

// OpenRead() Syntax
File.OpenRead(@"FilePath");
// ReadLine() Syntax
StreamReaderObject.ReadLine();

Código de exemplo:

using System.IO;
using System.Collections.Generic;
using System;

class ReadingCSV {
  static void Main(string[] args) {
    var reader = new StreamReader(File.OpenRead(@"D:\New folder\Data.csv"));
    List<string> listA = new List<string>();
    List<string> listB = new List<string>();
    while (!reader.EndOfStream) {
      var line = reader.ReadLine();
      var values = line.Split(',');

      listA.Add(values[0]);
      listB.Add(values[1]);
    }
    foreach (var column1 in listA) {
      Console.WriteLine(column1);
    }
    foreach (var column2 in listB) {
      Console.WriteLine(column2);
    }
  }
}

Saída:

Name
Alice
Bob
Age
30
25

Dividir na vírgula funciona quando nenhum campo contém ele próprio uma vírgula. Um campo entre aspas como "Smith, John" seria dividido no lugar errado; use TextFieldParser para arquivos assim.

Ler todas as linhas de uma vez com File.ReadAllLines

Quando o arquivo é pequeno o suficiente para caber na memória, File.ReadAllLines() é o caminho mais curto até um array: ele lê o arquivo inteiro e retorna cada linha como um elemento de um string[]. O exemplo abaixo divide cada linha, armazena cada linha de dados como seu próprio string[] e converte a lista de linhas em um array irregular com ToArray(), de modo que data[i] é a linha completa i. O loop começa no índice 1 para ignorar a linha de cabeçalho, ignora linhas em branco e verifica a quantidade de campos antes de indexar, portanto uma linha curta ou vazia não pode derrubá-lo.

Código de exemplo:

using System;
using System.Collections.Generic;
using System.IO;

class ReadingAllLines {
  static void Main() {
    string[] lines = File.ReadAllLines("Data.csv");
    List<string[]> rows = new List<string[]>();
    // Start at index 1 to skip the header row.
    for (int i = 1; i < lines.Length; i++) {
      if (string.IsNullOrWhiteSpace(lines[i])) {
        continue;
      }
      string[] values = lines[i].Split(',');
      if (values.Length < 2) {
        continue;
      }
      rows.Add(values);
    }
    string[][] data = rows.ToArray();
    foreach (string[] row in data) {
      Console.WriteLine(row[0] + ": " + row[1]);
    }
  }
}

Saída:

Alice: 30
Bob: 25

O compromisso é memória versus simplicidade. ReadAllLines() carrega o arquivo inteiro antes que a primeira linha seja processada e retorna um array que você pode indexar, contar e reler imediatamente. O loop com StreamReader mantém apenas uma linha por vez, portanto continua sendo a escolha mais segura para arquivos muito grandes cujas linhas você processa uma vez e descarta. Para arquivos pequenos que você consulta mais de uma vez, carregar tudo de uma vez é mais simples e rápido o bastante.

Analisar campos CSV com TextFieldParser

A classe TextFieldParser é definida no namespace Microsoft.VisualBasic.FileIO. Ela analisa um arquivo delimitado campo a campo e compreende campos entre aspas. Defina os delimitadores com SetDelimiters() e depois chame ReadFields() para obter os campos da linha atual como um array de strings; o analisador passa para a próxima linha a cada chamada. Em um projeto .NET Framework, adicione uma referência a Microsoft.VisualBasic antes de executar o programa abaixo; no .NET moderno, o namespace faz parte do framework compartilhado.

A sintaxe correta para usar este analisador é a seguinte:

TextFieldParser ParserName = new TextFieldParser(PathString);

Código de exemplo:

using System;
using System.Collections.Generic;
using Microsoft.VisualBasic.FileIO;

class ReadingCSV {
  public static void Main() {
    var path = @"D:\New folder\Data.csv";
    List<string> listA = new List<string>();
    List<string> listB = new List<string>();
    using (TextFieldParser csvReader = new TextFieldParser(path)) {
      csvReader.CommentTokens = new string[] { "#" };
      csvReader.SetDelimiters(new string[] { "," });
      csvReader.HasFieldsEnclosedInQuotes = true;

      // Skip the row with the column names
      csvReader.ReadLine();

      while (!csvReader.EndOfData) {
        // Read current line fields, pointer moves to the next line.
        string[] fields = csvReader.ReadFields();
        listA.Add(fields[0]);
        listB.Add(fields[1]);
      }
    }
    foreach (var column1 in listA) {
      Console.WriteLine(column1);
    }
    foreach (var column2 in listB) {
      Console.WriteLine(column2);
    }
  }
}

Saída:

Alice
Bob
30
25

Desta vez, a linha de cabeçalho é ignorada com o ReadLine() inicial, e o analisador, e não uma divisão manual, decide onde cada campo termina, de modo que uma vírgula entre aspas dentro de um campo permanece intacta.

Tratar campos entre aspas e aspas escapadas

As exportações reais colocam os campos entre aspas. Um campo que contém o delimitador é envolvido em aspas duplas, e uma aspa dupla dentro de um campo entre aspas é escrita duas vezes. O arquivo de exemplo People.csv abaixo usa as duas regras:

Name,Note
"Smith, John","Likes ""quoted"" text"
Jones,Plain note

Split(',') nada sabe sobre essas regras: ele corta a primeira linha de dados em 3 partes na vírgula dentro do nome. TextFieldParser as aplica: ele remove as aspas que envolvem o campo e transforma cada aspa duplicada em uma só, de modo que os campos voltam exatamente como foram concebidos. O programa imprime primeiro a quantidade de partes de Split(',') e depois os campos analisados.

Código de exemplo:

using System;
using Microsoft.VisualBasic.FileIO;

class ReadingQuotedCsv {
  public static void Main() {
    string rawLine = "\"Smith, John\",\"Likes \"\"quoted\"\" text\"";
    Console.WriteLine("Split(',') pieces: " + rawLine.Split(',').Length);
    using (TextFieldParser parser = new TextFieldParser("People.csv")) {
      parser.SetDelimiters(new string[] { "," });
      parser.HasFieldsEnclosedInQuotes = true;

      // Skip the row with the column names
      parser.ReadLine();

      while (!parser.EndOfData) {
        string[] fields = parser.ReadFields();
        Console.WriteLine(fields[0] + " | " + fields[1]);
      }
    }
  }
}

Saída:

Split(',') pieces: 3
Smith, John | Likes "quoted" text
Jones | Plain note

Se uma divisão manual produzir mais campos do que o cabeçalho tem colunas, a causa usual são os campos entre aspas. Troque esse arquivo para TextFieldParser em vez de tentar consertar as partes depois.

Erros comuns ao ler um arquivo CSV

  • IndexOutOfRangeException em values[1]: uma linha final em branco ou uma linha com uma coluna faltando se divide em menos campos do que o código espera. Ignore linhas só de espaços em branco e verifique a quantidade de campos antes de indexar, como faz o exemplo de ReadAllLines().

  • FileNotFoundException ou DirectoryNotFoundException: o caminho não existe na máquina que executa o programa. Um caminho absoluto codificado como D:\New folder\Data.csv só funciona no computador do autor; prefira um caminho relativo ou monte o caminho com Path.Combine(), e verifique-o com File.Exists() durante a depuração.

  • A linha de cabeçalho aparece como dado: o primeiro exemplo com StreamReader armazena Name e Age junto com as linhas reais. Quando um arquivo tem cabeçalho, ignore a primeira linha antes do loop, como fazem os outros exemplos.

  • O arquivo permanece bloqueado: um StreamReader que nunca é fechado mantém seu identificador até o coletor de lixo passar. Declare o leitor em um bloco using para que o arquivo seja fechado assim que o loop terminar, mesmo quando uma exceção o interrompa.