C# -  StreamReader, StringReader e  TextReader. Qual a diferença ? Qual usar ?


 

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 ?
 

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.

 

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)

 

Mas onde estão as classes TextReader/TextWriter , StreamReader/StreamWriter e StringReader/StringWriter ?

 

Se você esta curioso para saber basta olhar a figura abaixo:

 

 

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/StringReader  herdam de TextReader e StreamWriter/StringWriter  herdam de TextWriter.

 

Concluímos assim que tanto StreamReader como StringReader  são tipos de TextReader e que StreamWriter/StringWriter são tipos de TextWriter.

 

Muita calma nesta hora !!!

 

Mas qual a diferença então ???

 

A diferença básica esta na fonte de caracteres.

 

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.

 

Um StringReader lê os dados de outro string e um TextReader lê os dados de um arquivo texto.

 

Um TextReader possui toda lógica para leitura de caracteres e declara um 'contrato'  em como fazer a leitura de caracteres.

 

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

 

Um StringReader possui uma implementação específica para leitura de caracteres de uma string.

 

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

StringReader Implementa um TextReader que lê a partir de uma string.

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

 

Se eu preciso de uma rotina para ler Texto eu vou usar um TextReader que depois eu posso passar para um StreamReader ou StringReader.

 

Chegou a hora da prática.

 

No código abaixo, ingênuo por sinal, estamos o construtor da classe StreamReader() para criar uma instância de um TextReader(). Isso em programação orientada a objetos tem um nome : polimorfismo.

 

A classe StreamReader inclui métodos sobrecarregados adicionais que permitem que você especifique o arquivo de formas diferentes.

 

Neste exemplo abrimos o arquivo 'macoratti.txt' que deverá esta na mesma pasta que o arquivo executável da aplicação. (Na pasta Debug/bin):

                   Textreader tr = new StreamReader("macoratti.txt");

A seguir o programa lê uma linha do arquivo texto usando o método ReadLine() da instância TextReader(). Esta classe também inclui métodos que permitem invocar o método Read() ou usar o método Peek() para verificar o próximo caractere.

 

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

 

                Console.WriteLine(tr.ReadLine());
 

A seguir o código completo.

 

1- Lendo arquivo texto com StreamReader()

 

using System;
using System.IO;
namespace Macoratti
{
    class TextFileReader
    {
        static void Main(string[] args)
        {
            // cria um leitor e abre o arquivo
            Textreader tr = new StreamReader("macoratti.txt");
            // lê uma linha de texto
            Console.WriteLine(tr.ReadLine());
            // fecha o stream
            tr.Close();
        }
    }
}

 

2- Escrevendo em um arquivo texto com StreamWriter()

 

using System;
using System.IO;
namespace Macoratti
{
    class TextFileWriter
    {
        static void Main(string[] args)
        {
            // cria um escrito e abre o arquivo
            TextWriter tw = new StreamWriter("macoratti.txt");
            // escreve um a linha de texto no arquivo
            tw.WriteLine(DateTime.Now);
            // fecha o stream
            tw.Close();
        }
    }
}

 

Neste código o programa cria um arquivo texto chamado 'macoratti.txt' na mesma pasta onde esta o executável da aplicação. O arquivo vai conter a informação da data e hora da última vez que foi aberto para escrita.


Estamos instanciando a classe StreamWriter() que retorna um objeto do tipo TextWriter() (ai temos o polimorfismo novamente). A classe StreamWriter() é chamada com um único parâmetro que indica o nome do arquivo a ser aberto.

 

Se o arquivo não existir o StreamWriter() vai criar o arquivo:


    
TextWriter tw = new StreamWriter("date.txt");

 

Uma alternativa mais robusta ao código mostrado acima pode ser usada conforme o código a seguir:

 

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
                        while ((linha = sr.ReadLine()) != null)
                        {
                            Console.WriteLine(linha);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
            else
            {
                Console.WriteLine(" O arquivo " + arquivo + "não foi localizaod !");
            }
            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.

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

mas não quereis vir a mim para terdes vida!

Eu não recebo glória da parte dos homens;

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

Referências:


José Carlos Macoratti