C# -  Lendo e escrevendo em arquivos textos (StreamReader/StreamWriter)


Confuso com a profusão de classes e métodos disponíveis na plataforma .NET para ler e escrever em arquivos ?

 

Afinal quando vou acessar para ler ou escrever em um arquivo qual classe devo usar ?  

Como faço para arquivos textos ?

 

Antes de irmos direto para a parte prática vamos tentar ter uma visão geral da hierarquia de classes que a plataforma .NET oferece para esta finalidade.

 

Um pouco de teoria

 

As operações de input e output na  plataforma .NET são gerenciadas pela classe abstrata Stream que esta no namespace System.IO.

 

Se você conhece os fundamentos da programação orientada a objetos deve saber que uma classe abstrata não pode ser instanciada diretamente.

 

Portanto você não pode usar a classe Stream diretamente para acessar arquivos.

 

Ora bolas, então para que serve esta classe ?

 

Serve para padronizar e fornecer um contrato com métodos e propriedades que devem ser implementadas por outras classes que herdam da classe Stream.

 

Notou a palavra mágica herança ?

 

Pois bem a plataforma .NET oferece uma hierarquia de classes que podemos usar para realizar operações de input/output.(I/O)

 

A seguir vemos uma figura que mostra resumidamente a hierarquia de classes que herdam da classe Stream:

 

 

Podemos dizer que a classe Stream é a raiz das classes que tratam um fluxo de dados ou stream.

 

Como vemos existem muitas classes que derivam a classe Stream e podemos dividi-las em duas categorias:

A primeira categoria contém todas as classes que implementam a classe Stream para uma operação específica de I/O. Essas classes são:

A segunda categoria contém classes derivadas de Stream que reforçam o que a classe Stream tem a oferecer. Elas podem ser considerados como uma camada sobre a funcionalidade básica oferecida pela classe Stream.

Esta categoria inclui os seguintes classes:

Como todas as classes que tratam streams ou fluxo de dados herdam da classe Stream vejamos a seguir os métodos e propriedades desta classe que as demais classes tem que implementar:

 

1- Propriedades

 

CanRead obtém um valor indicando se o fluxo atual oferece suporte à leitura.
CanSeek obtém um valor indicando se o fluxo atual oferece suporte a busca.
CanTimeout Obtém um valor que determina se o fluxo atual da suporte ao time out.
CanWrite Obtém um valor indicando se o fluxo atual oferece suporte a escrita.
Length Obtém o tamanho em bytes do fluxo.
Position Obtém ou define a posição no fluxo atual.
ReadTimeout Obtém ou define um valor, em milisegundos, que determina quanto tempo o fluxo tentará ler antes do time out.
WriteTimeout Obtém ou define um valor, em milisegundos, que determina quanto tempo o fluxo tentará escrever antes do time out.

 

2- Métodos (Os principais)

 

BeginRead Inicia uma operação de leitura assíncrona. (Considere usar ReadAsync)
BeginWrite Inicia uma operação de gravação assíncrona. (Considere usar WriteAsync)
Close Fecha o fluxo atual e libera os recursos (como os soquetes e identificadores de arquivo) associados com o fluxo atual. Em vez de chamar esse método, certifique-se de que o fluxo seja descartado corretamente.
CopyTo(Stream) Le os bytes do fluxo atual e escreve-os em outro fluxo.
Dispose() Libera os recursos usados pelo Stream.
EndRead Aguarda a leitura assíncrona pendente terminar. (Considere usar ReadAsync)
EndWrite Termina uma operação de gravação assíncrono. (Considere usar WriteAsync)
Equals(Object) Verifica se o objeto especificado é igual ao objeto atual. (Herdado de Object.)
Finalize Permite que um objeto tente liberar recursos e executar outras operações de limpeza antes que ele seja recuperado pela coleta de lixo. (Herdado de Object.)
Flush Limpa todos os buffers para esse fluxo e faz com que alguns dados armazenados em buffer sejam gravados no dispositivo subjacente.
Read Lê uma sequência de bytes de fluxo atual e avança a posição no fluxo pelo número de bytes lidos.
Seek Define a posição no fluxo atual.
SetLength Define o comprimento de fluxo atual.
ToString Retorna uma string que representa o objeto atual. (Herdado de Object.)
Write Grava uma sequência de bytes no fluxo atual e avança a posição atual dentro desse fluxo pelo número de bytes escritos.

(fonte: http://msdn.microsoft.com/pt-br/library/system.io.stream%28v=vs.110%29.aspx - acessado em fevereiro de 2014)

 

Então para ler e escrever em arquivos textos podemos use as classes StreamReader/StreamWriter.  Essas classes derivam das classes TextReader/TextWriter.

 

Abaixo temos uma figura onde temos as classes do namespace System.IO mostrando a hierarquia de herança das principais classes para Input/OutPut.

 

 

As classes TextReader/TextWriter representam um leitor/escritor que podem ler/escrever uma série sequencial de caracteres. Elas são classes abstratas e não podem ser instanciadas diretamente.

 

Observe que as classes StreamReader/StreamWriter  herdam de TextReader e de TextWriter.

 

Concluímos assim que StreamReader é um tipo de TextReader e que StreamWriter é um tipo de TextWriter.

 

Assim um StreamReader obtém os seus dados de um fluxo ou stream que pode se representado por dados que estão na memória, em um arquivo ou vindo de uma porta serial, vídeo ou capturado em um dispositivo.

 

Dessa forma um StreamReader possui uma implementação específica para leitura de caracteres de um stream como um arquivo.

 

StreamReader Implementa um TextReader que lê caracteres de um fluxo de bytes em uma codificação específica.

TextReader Representa um leitor que pode ler uma série seqüencial de caracteres.(classe abstrata)

 

Conclusão: Se eu preciso de uma rotina para ler e/ou escrever em arquivos texto eu vou usar um StreamReader/StreamWriter.

 

Escrevendo e Lendo Arquivos Textos

 

Vamos começar mostrando como criar e escrever em arquivos texto usando StreamWriter.

 

A seguir as principais  propriedades e métodos da classe StreamWriter:

 

AutoFlush() Obtém ou define um valor indicando se o StreamWriter irá liberar o buffer no fluxo subjacente após cada chamada a Write.
Close() Fecha o objeto atual de StreamWriter e o fluxo subjacente
Dispose() Libera os recursos usados pelo objeto de TextWriter
Flush() Limpa todos os buffers para o gravador atual e faz com que todos os dados armazenados em buffer sejam gravados no fluxo subjacente.
NewLine()  
Write() Grava no fluxo.
WriteLine() Grava no fluxo seguido de um terminador de linha

 

Para começar temos que referenciar o namespace System.IO e declarar e inicializar uma nova instância de StreamWriter usando um de seus construtores.

StreamWriter writer = new StreamWriter("macoratti.txt")
Nesta declaração um novo StreamWriter é inicializado com o nome do arquivo igual 'macoratti.txt' 
(você pode usar qualquer extensão como .dat por exemplo)

Se o arquivo não existir ela será criado. No exemplo como não definimos um local para o arquivo ele será criado no mesmo diretório da aplicação.

 

Para escrever no arquivo podemos usar o método Write() ou WriteLine() . Estes métodos possuem diversas sobrecargas.

using (StreamWriter writer = new StreamWriter("macoratti.txt"))
{
   writer.Write("Macoratti .net ");
   writer.WriteLine("Quase tudo para Visual Basic");
   writer.WriteLine("http://www.macoratti.net");
}

No código o método Write() escreve uma linha e não anexa uma linha. Já o método WriteLine() anexa uma nova linha ("\r\n") ao arquivo a cada chamada.

 

Observe que estamos usando a cláusula using de forma a liberar os recursos usados na operação após sua utilização. Nunca esqueça de liberar os recursos usados e também verifique sempre se o arquivo existe quando necessário.

 

Se desejarmos escrever em um arquivo já existente anexando linhas ao arquivo podemos usar o construtor : StreamWriter(string caminho, bool anexa)

 

Inicializa uma nova instância do StreamWriter para o arquivo no caminho especificado, usando a codificação padrão e buffer. Se o arquivo existir, pode ser tanto sobrescrito ou anexado. Se o arquivo não existir, este construtor cria um novo arquivo.

 

using System.IO;

class Program
{
    static void Main()
    {
	// 1: Escreve um linha para o novo arquivo
	using (StreamWriter writer = new StreamWriter("C:\\dados\\macoratti.txt", true))
	{
	    writer.WriteLine("Macoratti .net");
	}
	// 2: Anexa uma linha ao arquivo
          using (StreamWriter writer = new StreamWriter(@"C:\dados\macoratti.txt", true))
	{
	    writer.WriteLine("Quase tudo para Visual Basic");
	}
    }
}

 

No código acima inicia inicializando um novo StreamWriter e abre o arquivo c:\dados\macoratti.txt no modo append. O argumento true do construtor especifica a operação para anexar dados ao arquivo.


No primeiro WriteLine() a string será anexada ao arquivo e no segundo WriteLine() estamos reabrindo o arquivo c:\dados\macoratti.txt e anexando a nova string ao arquivo.

 

É muito comum quando você estiver escrevendo em um arquivo usar um array ou uma lista de strings. A melhor forma de fazer isso é usar um laço após declarar uma instância de um StreamWriter conforme mostra o exemplo a seguir:

 

using System.IO;
class Program
{
    static void Main()
    {
    // Usamos um tipo var que mais simples
    using (var writer = new StreamWriter("macoratti.txt"))
    {
       // Percorre o laço
       for (int i = 0; i < 10; i++)
       {
         // Escreve uma string formatada no arquivo
         writer.Write("{0:0.0} ", i);
       }
    }
  }
}

 

No exemplo a instrução using abre e prepara o arquivo e após concluir fecha e libera os recursos usados.


Para ler um arquivo texto utilize a classe StreamReader() criando uma instância da classe e usando o construtor onde informa o nome do arquivo a ser aberto.

 

A seguir temos os principais membros da classe StreamReader:

 

Close() Fecha o StreamReader atual
Dispose() Libera os recursos usado pelo StreamReader
Peek() Retorna o próximo caractere disponível mas não o utiliza
Read() Lê o próximo caractere ou conjunto de caracteres de um stream
ReadBlock() Lê um número específico de caracteres de um stream atual e escreve os dados em um buffer
ReadLine() Lê uma linha de caractere do stream atual e retorna os dados em uma string
ReadToEnd() Lê o stream da posição atual até o fim do stream

 

No exemplo abaixo estamos acessando o arquivo 'macoratti.txt' e lendo uma linha do arquivo texto usando o método ReadLine(). É uma boa prática sempre verificar se o arquivo a ser lido existe ou se encontra no local indicado.

 

using System;
using System.IO;

class Program
{
    static void Main()
    {
      //usando a instrução using os recursos são liberados após a conclusão da operação
      string linha;
      using (StreamReader reader = new StreamReader("macoratti.txt"))
      {
         linha = reader.ReadLine();
      }
      Console.WriteLine(linha);
    }
}

 

A linha lida é exibida no console usando a instrução Console.WriteLine():

 

Para ler mais de uma linha do arquivo podemos usar um laço While e percorrer o arquivo enquanto não for encontrado um caractere null que indica o fim de arquivo.

 

using System;
using System.IO;

namespace Operacoes_IO
{
    class Program
    {
        static void Main(string[] args)
        {
            string arquivo = @"C:\dados\macoratti.txt";

            if (File.Exists(arquivo))
            {
                try
                {
                    using (StreamReader sr = new StreamReader(arquivo))
                    {
                        String linha;
                        // Lê linha por linha até o final do arquivo
                        while ((linha = sr.ReadLine()) != null)
                        {
                            Console.WriteLine(linha);
                        }

                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
            else
            {
                Console.WriteLine(" O arquivo " + arquivo + "não foi localizado !");
            }
            Console.ReadKey();
        }
    }
}

 

 

Este código utiliza a classe StreamReader() para ler o arquivo macoratti.txt na pasta c:\dados.

 

Estamos usando o método ReadLine() em um bloco While lendo linha a linha até encontrar o valor null que corresponde ao final do arquivo :   while ((linha = sr.ReadLine()) != null)

 

Estamos verificando se o arquivo existe usando o método estático Exists() da classe File:  if (File.Exists(arquivo))


Estamos usando também um bloco try/catch para tratar qualquer exceção que ocorre quando do acesso e da utilização do arquivo.

 

A cláusula using esta sendo usada para liberar de forma automática os recursos usados para acessar e ler o arquivo.

 

Dessa forma sempre que formos abrir um recurso como um arquivo primeiro devemos verificar e o mesmo existe e sempre devemos realizar o tratamento de exceção usando o bloco try/catch quando formos tratar arquivos com as classes do namespace System.IO.

 

Finalmente não devemos esquecer de liberar os recursos usados.

João 5:39 Examinais as Escrituras, porque julgais ter nelas a vida eterna; e são elas que dão testemunho de mim;

João 5:40 mas não quereis vir a mim para terdes vida!

João 5:41 Eu não recebo glória da parte dos homens;

João 5:42 mas bem vos conheço, que não tendes em vós o amor de Deus.

Referências:


José Carlos Macoratti