Agora que já vimos que podemos utilizar o C# para desenvolver um sistema orientado a objetos,
vamos aprender como utilizar as bibliotecas do System.IO
para ler e escrever dados em arquivos.
Leitura de arquivos
A entrada de dados no C# funciona em duas etapas. Na primeira etapa, temos uma classe abstrata
que representa uma sequência de bytes na qual podemos realizar operações de leitura e escrita. Essa classe abstrata é chamada de Stream
.
Como o Stream
é uma classe abstrata, não podemos usá-la diretamente, precisamos de uma
implementação para essa classe. No caso de leitura ou escrita em arquivos, utilizamos um tipo de
Stream
chamado FileStream
, que pode ser obtido através do método estático Open
da classe
File
. Quando utilizamos o Open
, devemos passar o nome do arquivo que será aberto e devemos
informá-lo o que queremos fazer com o arquivo (ler ou escrever).
Para abrirmos o arquivo entrada.txt
para leitura, utilizamos o código a seguir:
Stream entrada = File.Open("entrada.txt", FileMode.Open);
Agora que temos o Stream
, podemos ler seu próximo byte
utilizando o método ReadByte
.
byte b = entrada.ReadByte();
Porém, trabalhar com bytes não é fácil, queremos trabalhar com textos! Portanto vamos utilizar a segunda parte da leitura.
Para facilitar a leitura de Streams, o C# nos oferece uma classe chamada StreamReader
,
responsável por ler caracteres ou strings de um Stream
. O StreamReader
precisa saber qual
é a Stream
que será lida, portanto passaremos essa informação através de seu construtor:
StreamReader leitor = new StreamReader(entrada);
Para ler uma linha do arquivo, utilizamos o método ReadLine
do StreamReader
:
string linha = leitor.ReadLine();
Enquanto o arquivo não terminar, o método ReadLine()
devolve um valor diferente de nulo, portanto,
podemos ler todas as linhas de um arquivo com o seguinte código:
string linha = leitor.ReadLine();
while(linha != null)
{
MessageBox.Show(linha);
linha = leitor.ReadLine();
}
Assim que terminamos de trabalhar com o arquivo, devemos sempre lembrar de fechar o Stream
e o
StreamReader
:
leitor.Close();
entrada.Close();
O código completo para ler de um arquivo fica da seguinte forma:
Stream entrada = File.Open("entrada.txt", FileMode.Open);
StreamReader leitor = new StreamReader(entrada);
string linha = leitor.ReadLine();
while(linha != null)
{
MessageBox.Show(linha);
linha = leitor.ReadLine();
}
leitor.Close();
entrada.Close();
Porém, o arquivo pode não existir e, nesse caso, o C# lança a FileNotFoundException
.
Devemos, portanto, verificar se o arquivo existe antes de abri-lo para leitura. Podemos
verificar se um arquivo existe utilizando o método Exists
da classe File
:
if(File.Exists("entrada.txt"))
{
// Aqui temos certeza que o arquivo existe
}
O código da leitura com a verificação fica assim:
if(File.Exists("entrada.txt"))
{
Stream entrada = File.Open("entrada.txt", FileMode.Open);
StreamReader leitor = new StreamReader(entrada);
string linha = leitor.ReadLine();
while(linha != null)
{
MessageBox.Show(linha);
linha = leitor.ReadLine();
}
leitor.Close();
entrada.Close();
}
Lendo todo o conteúdo de um arquivo
Vimos que para ler todas as linhas de um arquivo, precisamos utilizar o método
ReadLine
até que o retorno seja o valornull
, mas isso é trabalhoso.Ao invés de chamar o método
ReadLine
para cada linha, podemos utilizar o métodoReadToEnd
da classeStreamReader
. Esse método devolve umastring
com todo o conteúdo do arquivo.
Escrevendo em arquivos
Assim como a leitura, a escrita também acontece em duas etapas. Na primeira etapa,
trabalhamos novamente escrevendo bytes para a saída. Para isso utilizaremos novamente a classe
abstrata Stream
.
Para escrevermos em um arquivo, precisamos primeiro abri-lo em modo de escrita utilizando o método
Open
do File
passando o modo FileMode.Create
:
Stream saida = File.Open("saida.txt", FileMode.Create);
Porém, não queremos trabalhar com Bytes, então utilizaremos uma classe especializada em escrever
em um Stream
chamada StreamWriter
.
StreamWriter escritor = new StreamWriter(saida);
Podemos escrever uma linha com o StreamWriter
utilizando o método WriteLine
:
escritor.WriteLine("minha mensagem");
Depois que terminamos de utilizar o arquivo, precisamos fechar todos os recursos:
escritor.Close();
saida.Close();
O código completo para escrever no arquivo fica da seguinte forma:
Stream saida = File.Open("saida.txt", FileMode.Create);
StreamWriter escritor = new StreamWriter(saida);
escritor.WriteLine("minha mensagem");
escritor.Close();
saida.Close();
Repare que, por usarmos uma classe abstrata, podemos então trocar facilmente a classe
concreta por outra. Por exemplo, poderíamos ler de um Socket, ou de uma porta serial,
e o código seria o mesmo: basta a classe ser filha de Stream
. Repare que o uso de
classes abstratas e polimorfismo nos possibilita ler/escrever em diferentes lugares com
o mesmo código. Veja que a própria Microsoft fez bom uso de orientação a objetos para
facilitar a vida dos desenvolvedores.
O IO do C# pode ser esquematizado pela seguinte figura:
Onde os arquivos são gravados
Quando passamos apenas o nome do arquivo no código do
File.Open
, o C# procura esse arquivo dentro da pasta em que a aplicação é executada. No caso de executarmos a aplicação pelo Visual Studio, a pasta utilizada pela aplicação será a pasta em que o projeto foi criado.
Gerenciando os arquivos com o using
Toda vez que abrimos um arquivo dentro de um programa C#, precisamos fechá-lo utilizando
o método Close
. Devemos garantir que o Close
será executado mesmo quando o código
lança uma exceção durante sua execução, para isso podemos utilizar o bloco finally
:
Stream arquivo = null;
StreamReader leitor = null;
try
{
arquivo = File.Open("arquivo.txt", FileMode.Open);
leitor = new StreamReader(arquivo);
// utiliza o arquivo
}
catch (Exception ex)
{
// Executa o tratamento do erro que aconteceu
}
finally
{
// fecha o arquivo e o leitor
// antes de fecharmos, precisamos verificar que o arquivo e o leitor foram
// realmente criados com sucesso
if(leitor != null)
{
leitor.Close();
}
if(arquivo != null)
{
arquivo.Close();
}
}
Veja que o código para lidar corretamente com os arquivos pode ficar muito complicado. Ao invés de cuidarmos manualmente dos arquivos, podemos pedir para a linguagem C# cuidar do gerenciamento utilizando o bloco using.
Dentro de um bloco using
podemos instanciar um recurso que queremos que seja gerenciado
pelo C#, como por exemplo um arquivo:
using (Stream arquivo = File.Open("arquivo.txt", FileMode.Open))
{
// o arquivo só fica aberto dentro desse bloco.
}
// se tentarmos utilizar o arquivo fora do bloco using teremos um erro de compilação.
Também podemos utilizar o using
para gerenciar o StreamReader
:
using(Stream arquivo = File.Open("arquivo.txt", FileMode.Open))
using(StreamReader leitor = new StreamReader(arquivo))
{
// aqui dentro você pode utilizar tanto o leitor quanto o arquivo
}
O using automaticamente fecha os arquivos utilizados dentro do bloco mesmo quando uma exceção é lançada pelo código.
Podemos utilizar o bloco using para gerenciar qualquer classe que implemente a interface IDisposable do C#.
Exercícios
Vamos agora criar um pequeno editor de texto para trabalharmos com arquivos. Dentro do Visual C#, crie um novo projeto do tipo
Windows Form Application
chamadoEditorDeTexto
. Dentro desse projeto, adicione umTextBox
que será o campo de texto onde o usuário digitará o texto que deve ser gravado no arquivo, chame-o detextoConteudo
. Além desse campo de texto, adicione também um botão que quando clicado gravará o campo de texto em um arquivo, chame-o debotaoGrava
.Para permitir que o usuário possa digitar diversas linhas no campo de texto, clique com o botão direito no
TextBox
e selecione a opçãoProperties
. Dentro da janelaProperties
, encontre a propriedade chamada Multiline e mude seu valor paratrue
. Agora estique oTextBox
para que o seu formulário fique parecido com o da imagem:Agora que temos o formulário pronto, faça com que o carregamento do programa preencha o campo de texto do formulário com o conteúdo de um arquivo chamado
texto.txt
. Não se esqueça de verificar que o arquivo existe antes de abri-loprivate void Form1_Load(object sender, EventArgs e) { if(File.Exists("texto.txt")) { Stream entrada = File.Open("texto.txt", FileMode.Open); StreamReader leitor = new StreamReader(entrada); string linha = leitor.ReadLine(); while(linha != null) { textoConteudo.Text += linha; linha = leitor.ReadLine(); } leitor.Close(); entrada.Close(); } }
Implemente a ação do botão Gravar. Quando clicado, esse botão deve gravar o conteúdo do
TextBox
dentro de um arquivo chamadotexto.txt
:private void botaoGrava_Click(object sender, EventArgs e) { Stream saida = File.Open("texto.txt", FileMode.Create); StreamWriter escritor = new StreamWriter(saida); escritor.Write(textoConteudo.Text); escritor.Close(); saida.Close(); }
Existe um método dentro da classe
StreamReader
chamadoReadToEnd
que lê todas as linhas do arquivo. Modifique o editor para utilizar esse método.Modifique o código do editor de texto para que ele utilize o
using
para fechar os arquivos.(Opcional) Quando queremos um programa que trabalha com o terminal do sistema operacional, precisamos criar um tipo diferente de projeto no
Visual Studio
, oConsole Application
.Para criarmos a aplicação que usa o terminal, devemos seguir os mesmos passos da criação do
Windows Form Application
, porém escolheremos oConsole Application
na janela do assistente.Quando criamos uma aplicação no console, o Visual Studio cria um novo projeto com uma classe que contém um método chamado
Main
. É esse método que será executado quando apertarmosF5
para rodar o programa.Dentro do
Main
, podemos imprimir uma mensagem no terminal utilizando oConsole.WriteLine
passando a mensagem:Console.WriteLine("Mensagem que vai para o terminal");
Quando queremos ler uma linha que o usuário digitou no terminal, utilizamos um atributo do tipo
TextReader
da classeConsole
chamadoIn
:TextReader leitor = Console.In;
No
TextReader
, temos o métodoReadLine
que consegue ler uma linha do terminal.string linha = leitor.ReadLine();
O
ReadLine
devolve uma string não nula, enquanto o usuário continuar enviando novas linhas.while(linha != null) { // usa o texto da linha atual linha = leitor.ReadLine(); }
Quando o usuário manda a combinação
Ctrl+z
para a aplicação, o leitor devolve null.Crie um programa que lê e imprime as linhas que o usuário digita no terminal até que seja enviada a combinação
Ctrl+z
.(Opcional) Quando fizemos a leitura de um arquivo, utilizamos o código:
using(Stream entrada = File.Open("entrada.txt", FileMode.Open)) using(StreamReader leitor = new StreamReader(entrada)) { // usa o leitor }
No C#, o
StreamReader
é uma subclasse da classe abstrataTextReader
, a mesma que utilizamos para ler dados do terminal, logo podemos reescrever o código da leitura de arquivo para:using(Stream entrada = File.Open("entrada.txt", FileMode.Open)) using(TextReader leitor = new StreamReader(entrada)) { // usa o leitor }
Quais modificações deveríamos fazer nesse código para ler o texto que o usuário digitou no terminal?
Para saber mais — onde colocar os arquivos da aplicação
Precisamos tomar muito cuidado ao escrever programas que guardam informações dentro de arquivos. Como dito
anteriormente, quando utilizamos o File.Open
, o C# procura o arquivo na pasta em que a aplicação está sendo
executada, porém muitas vezes os programas escritos são instalados em pastas do sistema operacional, por exemplo
C:/Arquivos de Programas
, nesse caso o programa tentará escrever as informações dentro de um pasta do sistema
operacional e por isso, ele só pode ser executado por um administrador do sistema.
Normalmente, quando escrevemos uma aplicação com algum erro de programação, isso não afeta o sistema operacional pois o programa não é executado com permissões de administrador e, portanto, não pode fazer modificações perigosas no sistema. Então, para que a aplicação não precise ser executado como administrador, podemos fazer com que ela escreva, por exemplo, na pasta de documentos do usuário logado.
Quando queremos recuperar o caminho para uma pasta especial do sistema operacional, podemos utilizar uma classe do
C# chamada Environment
do namespace System
. Nessa classe, podemos invocar o método GetFolderPath
para recuperar
o caminho para uma pasta do sistema. O método GetFolderPath
recebe como argumento uma constante que indica qual é
a pasta que queremos. Para recuperarmos o caminho para a pasta de documentos do usuário logado, podemos utilizar o seguinte código:
string pastaDocumentos = Environment.GetFolderPath(
Environment.SpecialFolder.MyDocuments);
Os outros valores aceitos pelo método GetFolderPath podem ser encontrados nessa página: http://msdn.microsoft.com/en-us/library/system.environment.specialfolder.aspx
Agora se quisermos abrir um arquivo chamado entrada.txt
dentro da pasta de documentos, precisamos combinar o caminho da pasta com o nome do arquivo. Para resolver esse problema, utilizamos o método Combine
da classe
Path
do namespace System.IO
:
string pastaDocumentos = Environment.GetFolderPath(
Environment.SpecialFolder.MyDocuments);
string caminhoArquivo = Path.Combine(pastaDocumentos, "entrada.txt");